Skip to content

Commit 13a0c7f

Browse files
committed
jooby-validation: skip double validation when filter is on lambda and controller
- jooby-apt: set the `io.jooby.validation.BeanValidator:true` route attribute - fix #3530
1 parent 454c132 commit 13a0c7f

7 files changed

Lines changed: 117 additions & 2 deletions

File tree

jooby/src/main/java/io/jooby/ForwardingContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,10 @@ public Context setUser(@Nullable Object user) {
619619
return this;
620620
}
621621

622+
public Context getDelegate() {
623+
return ctx;
624+
}
625+
622626
@NonNull @Override
623627
public Object forward(@NonNull String path) {
624628
Object result = ctx.forward(path);

jooby/src/main/java/io/jooby/validation/BeanValidator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ static Route.Filter validate() {
7878
static Route.Handler validate(Route.Handler next) {
7979
return ctx -> {
8080
try {
81+
// The io.jooby.validation.BeanValidator attribute is set by jooby-apt. Skip validation
82+
// let generated code to invoke BeanValidator.
83+
if (ctx.getRoute().getAttributes().containsKey(BeanValidator.class.getName())) {
84+
return next.apply(ctx);
85+
}
8186
return next.apply(new ValidationContext(ctx));
8287
} catch (UndeclaredThrowableException | InvocationTargetException e) {
8388
// unwrap reflective exception

modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public List<String> generateMapping(boolean kt) {
9999
var returnType = getReturnType();
100100
var paramString = String.join(", ", getJavaMethodSignature(kt));
101101
var javadocLink = javadocComment(kt);
102-
var attributeGenerator = new RouteAttributesGenerator(context);
102+
var attributeGenerator = new RouteAttributesGenerator(context, hasBeanValidation);
103103
var routes = router.getRoutes();
104104
var lastRoute = routes.get(routes.size() - 1).equals(this);
105105
var entries = annotationMap.entrySet().stream().toList();

modules/jooby-apt/src/main/java/io/jooby/internal/apt/RouteAttributesGenerator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,13 @@ private record EnumValue(String type, String value) {}
4646

4747
private final List<String> skip;
4848
private final Types types;
49+
private final boolean hasBeanValidation;
4950

50-
public RouteAttributesGenerator(MvcContext context) {
51+
public RouteAttributesGenerator(MvcContext context, boolean hasBeanValidation) {
5152
var environment = context.getProcessingEnvironment();
5253
this.types = environment.getTypeUtils();
5354
this.skip = Options.stringListOpt(environment, SKIP_ATTRIBUTE_ANNOTATIONS);
55+
this.hasBeanValidation = hasBeanValidation;
5456
}
5557

5658
public Optional<String> toSourceCode(boolean kt, MvcRoute route, int indent) {
@@ -137,6 +139,9 @@ private Map<String, Object> annotationMap(ExecutableElement method) {
137139
var attributes = annotationMap(method.getEnclosingElement().getAnnotationMirrors());
138140
// method
139141
attributes.putAll(annotationMap(method.getAnnotationMirrors()));
142+
if (hasBeanValidation) {
143+
attributes.put("io.jooby.validation.BeanValidator", true);
144+
}
140145
return attributes;
141146
}
142147

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.i3530;
7+
8+
import jakarta.validation.constraints.NotEmpty;
9+
10+
public class Bean3530 {
11+
@NotEmpty private String name;
12+
13+
public @NotEmpty String getName() {
14+
return name;
15+
}
16+
17+
public void setName(@NotEmpty String name) {
18+
this.name = name;
19+
}
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.i3530;
7+
8+
import io.jooby.annotation.POST;
9+
import jakarta.validation.Valid;
10+
11+
public class Controller3530 {
12+
13+
@POST("/3530/controller")
14+
public Bean3530 create(@Valid Bean3530 value) {
15+
return value;
16+
}
17+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.i3530;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
import io.jooby.StatusCode;
11+
import io.jooby.hibernate.validator.HibernateValidatorModule;
12+
import io.jooby.jackson.JacksonModule;
13+
import io.jooby.junit.ServerTest;
14+
import io.jooby.junit.ServerTestRunner;
15+
import io.jooby.validation.BeanValidator;
16+
import io.jooby.validation.ValidationContext;
17+
import okhttp3.MediaType;
18+
import okhttp3.RequestBody;
19+
20+
public class Issue3530 {
21+
22+
@ServerTest
23+
public void shouldRunValidationOnce(ServerTestRunner runner) {
24+
runner
25+
.define(
26+
app -> {
27+
app.install(new JacksonModule());
28+
app.install(new HibernateValidatorModule());
29+
30+
app.use(BeanValidator.validate());
31+
32+
app.post(
33+
"/3530/lambda",
34+
ctx -> {
35+
assertInstanceOf(ValidationContext.class, ctx);
36+
return ctx.body(Bean3530.class);
37+
});
38+
39+
// Must NOT be validation context:
40+
app.use(
41+
next ->
42+
ctx -> {
43+
assertFalse(ctx instanceof ValidationContext);
44+
return next.apply(ctx);
45+
});
46+
app.mvc(new Controller3530_());
47+
})
48+
.ready(
49+
http -> {
50+
http.post(
51+
"/3530/lambda",
52+
RequestBody.create("{}", MediaType.parse("application/json")),
53+
rsp -> {
54+
assertEquals(StatusCode.UNPROCESSABLE_ENTITY.value(), rsp.code());
55+
});
56+
http.post(
57+
"/3530/controller",
58+
RequestBody.create("{}", MediaType.parse("application/json")),
59+
rsp -> {
60+
assertEquals(StatusCode.UNPROCESSABLE_ENTITY.value(), rsp.code());
61+
});
62+
});
63+
}
64+
}

0 commit comments

Comments
 (0)