Skip to content

Commit 2f732b8

Browse files
committed
supports null body
- ctx.body().value() must throws a MissingValueException when empty/missing fix jooby-project#1890 - Null/Empty body param mapped to empty string ("") fix jooby-project#1859
1 parent 33535db commit 2f732b8

File tree

14 files changed

+175
-24
lines changed

14 files changed

+175
-24
lines changed

jooby/src/main/java/io/jooby/Body.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.MissingValueException;
89
import io.jooby.internal.ByteArrayBody;
910
import io.jooby.internal.FileBody;
1011
import io.jooby.internal.InputStreamBody;
@@ -37,7 +38,11 @@ public interface Body extends ValueNode {
3738
* @return Body as string.
3839
*/
3940
default @Nonnull String value(@Nonnull Charset charset) {
40-
return new String(bytes(), charset);
41+
byte[] bytes = bytes();
42+
if (bytes.length == 0) {
43+
throw new MissingValueException("body");
44+
}
45+
return new String(bytes, charset);
4146
}
4247

4348
/**

jooby/src/main/java/io/jooby/DefaultContext.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,7 @@ public interface DefaultContext extends Context {
378378
try {
379379
if (MediaType.text.equals(contentType)) {
380380
T result = ValueConverters.convert(body(), type, getRouter());
381-
if (result != null) {
382-
return result;
383-
}
381+
return result;
384382
}
385383
return (T) decoder(contentType).decode(this, type);
386384
} catch (Exception x) {

modules/jooby-apt/src/main/java/io/jooby/internal/apt/asm/BodyWriter.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
package io.jooby.internal.apt.asm;
77

88
import io.jooby.Body;
9+
import io.jooby.Value;
910
import io.jooby.internal.apt.ParamDefinition;
1011
import org.objectweb.asm.ClassWriter;
1112
import org.objectweb.asm.MethodVisitor;
1213

1314
import java.io.InputStream;
1415
import java.lang.reflect.Method;
1516
import java.nio.channels.ReadableByteChannel;
17+
import java.nio.charset.Charset;
1618
import java.util.Map;
1719

1820
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
@@ -42,6 +44,18 @@ public void accept(ClassWriter writer, String handlerInternalName, MethodVisitor
4244
Method channel = Body.class.getDeclaredMethod("channel");
4345
visitor.visitMethodInsn(INVOKEINTERFACE, "io/jooby/Body", channel.getName(),
4446
getMethodDescriptor(channel), true);
47+
} else if (parameter.is(String.class)) {
48+
visitor.visitMethodInsn(INVOKEINTERFACE, CTX.getInternalName(), paramMethod.getName(),
49+
getMethodDescriptor(paramMethod), true);
50+
String methodName;
51+
if (parameter.isNullable()) {
52+
methodName = "valueOrNull";
53+
} else {
54+
methodName = "value";
55+
}
56+
Method value = Value.class.getDeclaredMethod(methodName);
57+
visitor.visitMethodInsn(INVOKEINTERFACE, "io/jooby/Value", value.getName(),
58+
getMethodDescriptor(value), true);
4559
} else {
4660
Method convertMethod = parameter.getMethod();
4761
if (!convertMethod.getName().equals("body")) {

modules/jooby-apt/src/test/java/source/Provisioning.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.jooby.annotations.PathParam;
2020
import io.jooby.annotations.QueryParam;
2121

22+
import javax.annotation.Nonnull;
2223
import java.io.InputStream;
2324
import java.math.BigDecimal;
2425
import java.math.BigInteger;
@@ -315,9 +316,15 @@ public void sideEffect(Context ctx) {
315316
ctx.send(StatusCode.CREATED);
316317
}
317318

319+
@POST
320+
@Path("/bodyStringParamNullable")
321+
public String bodyStringParamNullable(String body) {
322+
return body;
323+
}
324+
318325
@POST
319326
@Path("/bodyStringParam")
320-
public String bodyStringParam(String body) {
327+
public String bodyStringParam(@Nonnull String body) {
321328
return body;
322329
}
323330

modules/jooby-apt/src/test/java/tests/HandlerCompilerTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,22 @@ public void body() throws Exception {
359359
.module(app -> {
360360
MockRouter router = new MockRouter(app);
361361

362+
Body body = mock(Body.class);
363+
when(body.valueOrNull()).thenReturn("...");
364+
365+
Context ctx = mockContext("POST", "/p/bodyStringParamNullable");
366+
when(ctx.body()).thenReturn(body);
367+
368+
assertEquals("...", router.post("/p/bodyStringParamNullable", ctx).value());
369+
})
370+
.module(app -> {
371+
MockRouter router = new MockRouter(app);
372+
373+
Body body = mock(Body.class);
374+
when(body.value()).thenReturn("...");
375+
362376
Context ctx = mockContext("POST", "/p/bodyStringParam");
363-
when(ctx.body(String.class)).thenReturn("...");
377+
when(ctx.body()).thenReturn(body);
364378

365379
assertEquals("...", router.post("/p/bodyStringParam", ctx).value());
366380
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tests.i1859;
2+
3+
import io.jooby.annotations.POST;
4+
import io.jooby.annotations.Path;
5+
6+
import java.util.Optional;
7+
8+
@Path(("/c"))
9+
public class C1859 {
10+
@POST("/i1859")
11+
public String foo(String theBodyParam) {
12+
return Optional.ofNullable(theBodyParam).orElse("empty");
13+
}
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package tests.i1859;
2+
3+
import io.jooby.Extension;
4+
import io.jooby.MvcFactory;
5+
import io.jooby.Route;
6+
7+
import javax.annotation.Nonnull;
8+
import javax.inject.Provider;
9+
10+
public class Expected1859 implements MvcFactory {
11+
12+
@Override public boolean supports(@Nonnull Class type) {
13+
return false;
14+
}
15+
16+
@Nonnull @Override public Extension create(@Nonnull Provider provider) {
17+
return application -> {
18+
Route route = application.get("/c/1859", ctx -> {
19+
C1859 controller = (C1859) provider.get();
20+
String value = ctx.body().valueOrNull();
21+
return controller.foo(value);
22+
});
23+
route.setReturnType(String.class);
24+
};
25+
}
26+
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package tests.i1859;
2+
3+
import io.jooby.MockContext;
4+
import io.jooby.MockRouter;
5+
import io.jooby.apt.MvcModuleCompilerRunner;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
public class Issue1859 {
11+
12+
@Test
13+
public void shouldGetNullOnMissingBody() throws Exception {
14+
new MvcModuleCompilerRunner(new C1859())
15+
.example(Expected1859.class)
16+
.debugModule(app -> {
17+
MockRouter router = new MockRouter(app);
18+
MockContext ctx = new MockContext();
19+
ctx.setBody(new byte[0]);
20+
assertEquals("empty", router.post("/c/i1859", ctx).value().toString());
21+
});
22+
}
23+
24+
}

modules/jooby-test/src/main/java/io/jooby/MockContext.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ public MockContext setFile(@Nonnull String name, @Nonnull FileUpload file) {
353353

354354
@Nonnull @Override public <T> T decode(@Nonnull Type type, @Nonnull MediaType contentType) {
355355
if (bodyObject == null) {
356-
throw new IllegalStateException("No body was set, use setBody() to set one.");
356+
throw new IllegalStateException("No body was set, use setBodyObject() to set one.");
357357
}
358358
Reified<?> reified = Reified.get(type);
359359
if (!reified.getRawType().isInstance(bodyObject)) {
@@ -368,12 +368,19 @@ public MockContext setFile(@Nonnull String name, @Nonnull FileUpload file) {
368368
* @param body Request body.
369369
* @return This context.
370370
*/
371-
@Nonnull public MockContext setBody(@Nonnull Object body) {
372-
if (body instanceof Body) {
373-
this.body = (Body) body;
374-
} else {
375-
this.bodyObject = body;
376-
}
371+
@Nonnull public MockContext setBody(@Nonnull Body body) {
372+
this.body = body;
373+
return this;
374+
}
375+
376+
/**
377+
* Set request body.
378+
*
379+
* @param body Request body.
380+
* @return This context.
381+
*/
382+
@Nonnull public MockContext setBodyObject(@Nonnull Object body) {
383+
this.bodyObject = body;
377384
return this;
378385
}
379386

@@ -395,7 +402,7 @@ public MockContext setFile(@Nonnull String name, @Nonnull FileUpload file) {
395402
* @return This context.
396403
*/
397404
@Nonnull public MockContext setBody(@Nonnull byte[] body) {
398-
this.body = Body.of(this, new ByteArrayInputStream(body), body.length);
405+
setBody(Body.of(this, new ByteArrayInputStream(body), body.length));
399406
return this;
400407
}
401408

modules/jooby-test/src/test/java/io/jooby/UnitTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ public void unitTests() {
6161
});
6262

6363
PojoBody pojo = new PojoBody();
64-
router.post("/pojo", new MockContext().setBody(pojo), result -> {
64+
router.post("/pojo", new MockContext().setBodyObject(pojo), result -> {
6565
assertEquals(pojo, result.value());
6666
});
6767

68-
router.get("/x/notfound", new MockContext().setBody(pojo), result -> {
68+
router.get("/x/notfound", new MockContext().setBodyObject(pojo), result -> {
6969
assertEquals(StatusCode.NOT_FOUND, result.getStatusCode());
7070
});
7171
}

0 commit comments

Comments
 (0)