Skip to content

Commit 229b584

Browse files
error handler, tests, refactoring
1 parent 646f355 commit 229b584

27 files changed

Lines changed: 704 additions & 74 deletions

File tree

jooby/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@
4949
<artifactId>jakarta.inject-api</artifactId>
5050
</dependency>
5151

52-
<!-- jakarta.validation -->
53-
<dependency>
54-
<groupId>jakarta.validation</groupId>
55-
<artifactId>jakarta.validation-api</artifactId>
56-
</dependency>
57-
5852
<!-- config -->
5953
<dependency>
6054
<groupId>com.typesafe</groupId>

modules/jooby-apt/pom.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121

2222
<dependency>
2323
<groupId>io.jooby</groupId>
24-
<artifactId>jooby-validation</artifactId>
24+
<artifactId>jooby</artifactId>
2525
<version>${jooby.version}</version>
26+
<scope>test</scope>
2627
</dependency>
2728

2829
<dependency>
2930
<groupId>io.jooby</groupId>
30-
<artifactId>jooby</artifactId>
31+
<artifactId>jooby-jakarta-validation</artifactId>
3132
<version>${jooby.version}</version>
3233
<scope>test</scope>
3334
</dependency>

modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
105105
return false;
106106
} else {
107107
var routeMap = buildRouteRegistry(annotations, roundEnv);
108+
verifyBeanValidationDependency(routeMap.values());
108109
for (var router : routeMap.values()) {
109110
try {
110111
var sourceCode = router.toSourceCode(null);
@@ -371,4 +372,21 @@ public static RuntimeException propagate(final Throwable x) {
371372
private static <E extends Throwable> E sneakyThrow0(final Throwable x) throws E {
372373
throw (E) x;
373374
}
375+
376+
private void verifyBeanValidationDependency(Collection<MvcRouter> routers) {
377+
var hasBeanValidation = routers.stream().anyMatch(MvcRouter::hasBeanValidation);
378+
if (hasBeanValidation) {
379+
TypeElement validatorElement = processingEnv.getElementUtils()
380+
.getTypeElement("io.jooby.validation.BeanValidator");
381+
382+
if (validatorElement == null) {
383+
processingEnv.getMessager().printMessage(
384+
Diagnostic.Kind.ERROR,
385+
"Unable to load 'BeanValidator' class. " +
386+
"Bean validation usage (@Valid) was detected, but the appropriate dependency is missing. " +
387+
"Please ensure that you have added the corresponding validation dependency " +
388+
"(e.g., jooby-hbv).");
389+
}
390+
}
391+
}
374392
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ public class MvcRoute {
3030
private String generatedName;
3131
private final boolean suspendFun;
3232
private boolean uncheckedCast;
33+
private final boolean hasBeanValidation;
3334

3435
public MvcRoute(MvcContext context, MvcRouter router, ExecutableElement method) {
3536
this.context = context;
3637
this.router = router;
3738
this.method = method;
3839
this.parameters =
3940
method.getParameters().stream().map(it -> new MvcParameter(context, this, it)).toList();
41+
this.hasBeanValidation = parameters.stream().anyMatch(MvcParameter::isRequireBeanValidation);
4042
this.suspendFun =
4143
!parameters.isEmpty()
4244
&& parameters.get(parameters.size() - 1).getType().is("kotlin.coroutines.Continuation");
@@ -51,6 +53,7 @@ public MvcRoute(MvcContext context, MvcRouter router, MvcRoute route) {
5153
this.method = route.method;
5254
this.parameters =
5355
method.getParameters().stream().map(it -> new MvcParameter(context, this, it)).toList();
56+
this.hasBeanValidation = parameters.stream().anyMatch(MvcParameter::isRequireBeanValidation);
5457
this.returnType =
5558
new TypeDefinition(
5659
context.getProcessingEnvironment().getTypeUtils(), method.getReturnType());
@@ -206,7 +209,7 @@ public List<String> generateHandlerCall(boolean kt) {
206209
String generatedParameter = parameter.generateMapping(kt);
207210
if (parameter.isRequireBeanValidation()) {
208211
generatedParameter = CodeBlock.of(
209-
"io.jooby.validation.ValidationHelper.validate(",
212+
"io.jooby.validation.BeanValidator.validate(",
210213
"ctx, ",
211214
generatedParameter,
212215
")");
@@ -491,4 +494,8 @@ private String javadocComment(boolean kt) {
491494
public void setUncheckedCast(boolean value) {
492495
this.uncheckedCast = value;
493496
}
497+
498+
public boolean hasBeanValidation() {
499+
return hasBeanValidation;
500+
}
494501
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,4 +362,8 @@ public String toString() {
362362
buffer.append("}");
363363
return buffer.toString();
364364
}
365+
366+
public boolean hasBeanValidation() {
367+
return getRoutes().stream().anyMatch(MvcRoute::hasBeanValidation);
368+
}
365369
}

modules/jooby-apt/src/test/java/tests/validation/Bean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package tests.validation;
22

3+
import io.jooby.Context;
4+
35
class Bean {
46
private String name;
57

@@ -10,4 +12,8 @@ public String getName() {
1012
public void setName(String name) {
1113
this.name = name;
1214
}
15+
16+
public static Bean map(Context ctx) {
17+
return ctx.body(Bean.class);
18+
}
1319
}

modules/jooby-apt/src/test/java/tests/validation/BeanValidationGeneratorTest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,24 @@ public void generate_validation_forBean() throws Exception {
1313
false,
1414
source -> {
1515
assertTrue(source.contains(
16-
"c.validateQueryBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.query(\"bean\").isMissing() ? ctx.query().toNullable(tests.validation.Bean.class) : ctx.query(\"bean\").toNullable(tests.validation.Bean.class)))")
16+
"c.validateQueryBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.query(\"bean\").isMissing() ? ctx.query().toNullable(tests.validation.Bean.class) : ctx.query(\"bean\").toNullable(tests.validation.Bean.class)))")
1717
);
1818

1919
assertTrue(source.contains(
20-
"c.validateFormBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.form(\"bean\").isMissing() ? ctx.form().toNullable(tests.validation.Bean.class) : ctx.form(\"bean\").toNullable(tests.validation.Bean.class)))")
20+
"c.validateFormBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.form(\"bean\").isMissing() ? ctx.form().toNullable(tests.validation.Bean.class) : ctx.form(\"bean\").toNullable(tests.validation.Bean.class)))")
2121
);
2222

2323
assertTrue(source.contains(
24-
"c.validateBodyBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.body(tests.validation.Bean.class)))")
24+
"c.validateBindParamBean(io.jooby.validation.BeanValidator.validate(ctx, tests.validation.Bean.map(ctx)))")
2525
);
2626

27+
assertTrue(source.contains(
28+
"c.validateBodyBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.body(tests.validation.Bean.class)))")
29+
);
30+
31+
assertTrue(source.contains(
32+
"c.validateListOfBodyBeans(io.jooby.validation.BeanValidator.validate(ctx, ctx.body(io.jooby.Reified.list(tests.validation.Bean.class).getType())))")
33+
);
2734
});
2835
}
2936
}

modules/jooby-apt/src/test/java/tests/validation/BeanValidationsController.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package tests.validation;
22

3-
import io.jooby.annotation.FormParam;
4-
import io.jooby.annotation.POST;
5-
import io.jooby.annotation.QueryParam;
3+
import io.jooby.annotation.*;
64
import jakarta.validation.Valid;
75

6+
import java.util.List;
7+
88
public class BeanValidationsController {
99

1010
@POST("/validate/query-bean")
@@ -17,9 +17,24 @@ public Bean validateFormBean(@Valid @FormParam Bean bean) {
1717
return bean;
1818
}
1919

20+
//todo: revive when flash `toNullable` will be fixed
21+
// @POST("/validate/flash-bean")
22+
// public Bean validateFlashBean(@Valid @FlashParam Bean bean) {
23+
// return bean;
24+
// }
25+
26+
@POST("/validate/bind-param-bean")
27+
public Bean validateBindParamBean(@Valid @BindParam Bean bean) {
28+
return bean;
29+
}
30+
2031
@POST("/validate/body-bean")
2132
public Bean validateBodyBean(@Valid Bean bean) {
2233
return bean;
2334
}
2435

36+
@POST("/validate/list-of-body-beans")
37+
public List<Bean> validateListOfBodyBeans(@Valid List<Bean> bean) {
38+
return bean;
39+
}
2540
}

modules/jooby-hbv/pom.xml

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222
<dependency>
2323
<groupId>io.jooby</groupId>
24-
<artifactId>jooby-validation</artifactId>
24+
<artifactId>jooby-jakarta-validation</artifactId>
2525
<version>${jooby.version}</version>
2626
</dependency>
2727

28-
<!-- Hibernate Validator -->
28+
<!-- Hibernate Validator -->
2929
<dependency>
3030
<groupId>org.hibernate.validator</groupId>
3131
<artifactId>hibernate-validator</artifactId>
@@ -43,26 +43,5 @@
4343
<artifactId>expressly</artifactId>
4444
<version>5.0.0</version>
4545
</dependency>
46-
47-
<!-- Test dependencies -->
48-
<dependency>
49-
<groupId>org.junit.jupiter</groupId>
50-
<artifactId>junit-jupiter-engine</artifactId>
51-
<scope>test</scope>
52-
</dependency>
53-
54-
<dependency>
55-
<groupId>org.jacoco</groupId>
56-
<artifactId>org.jacoco.agent</artifactId>
57-
<classifier>runtime</classifier>
58-
<scope>test</scope>
59-
</dependency>
60-
61-
<dependency>
62-
<groupId>org.mockito</groupId>
63-
<artifactId>mockito-core</artifactId>
64-
<scope>test</scope>
65-
</dependency>
66-
6746
</dependencies>
6847
</project>

modules/jooby-hbv/src/main/java/io/jooby/hbv/HbvModule.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import edu.umd.cs.findbugs.annotations.NonNull;
1010
import io.jooby.Extension;
1111
import io.jooby.Jooby;
12+
import io.jooby.StatusCode;
13+
import io.jooby.validation.ConstraintViolationHandler;
14+
import jakarta.validation.ConstraintViolationException;
1215
import jakarta.validation.Validator;
1316
import jakarta.validation.ValidatorFactory;
1417
import org.hibernate.validator.HibernateValidator;
@@ -17,19 +20,64 @@
1720
import java.util.function.Consumer;
1821

1922
import static jakarta.validation.Validation.byProvider;
20-
import static java.util.Objects.requireNonNull;
2123

2224
public class HbvModule implements Extension {
2325

2426
private Consumer<HibernateValidatorConfiguration> configurer;
27+
private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY;
28+
private String title = "Validation failed";
29+
private boolean disableDefaultViolationHandler = false;
2530

26-
public HbvModule doWith(final Consumer<HibernateValidatorConfiguration> configurer) {
27-
this.configurer = requireNonNull(configurer, "Configurer callback is required.");
31+
/**
32+
* Setups a configurer callback.
33+
*
34+
* @param configurer Configurer callback.
35+
* @return This module.
36+
*/
37+
public HbvModule doWith(@NonNull final Consumer<HibernateValidatorConfiguration> configurer) {
38+
this.configurer = configurer;
39+
return this;
40+
}
41+
42+
/**
43+
* Overrides the default status code for the errors produced by validation.
44+
* Default code is UNPROCESSABLE_ENTITY(422)
45+
*
46+
* @param statusCode new status code
47+
* @return This module.
48+
*/
49+
public HbvModule statusCode(@NonNull StatusCode statusCode) {
50+
this.statusCode = statusCode;
51+
return this;
52+
}
53+
54+
/**
55+
* Overrides the default title for the errors produced by validation.
56+
* Default title is "Validation failed"
57+
*
58+
* @param title new title
59+
* @return This module.
60+
*/
61+
public HbvModule validationTitle(@NonNull String title) {
62+
this.title = title;
63+
return this;
64+
}
65+
66+
/**
67+
* Disables default constraint violation handler.
68+
* By default {@link HbvModule} provide built-in error handler for the {@link ConstraintViolationException}
69+
* Such exceptions are transformed into response of {@link io.jooby.validation.ValidationResult}
70+
* Use this flag to disable default error handler and provide your custom.
71+
*
72+
* @return This module.
73+
*/
74+
public HbvModule disableViolationHandler() {
75+
this.disableDefaultViolationHandler = true;
2876
return this;
2977
}
3078

3179
@Override
32-
public void install(@NonNull Jooby application) {
80+
public void install(@NonNull Jooby app) {
3381
HibernateValidatorConfiguration cfg = byProvider(HibernateValidator.class).configure();
3482

3583
if (configurer != null) {
@@ -38,7 +86,11 @@ public void install(@NonNull Jooby application) {
3886

3987
try (ValidatorFactory factory = cfg.buildValidatorFactory()) {
4088
Validator validator = factory.getValidator();
41-
application.getServices().put(Validator.class, validator);
89+
app.getServices().put(Validator.class, validator);
90+
91+
if (!disableDefaultViolationHandler) {
92+
app.error(ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title));
93+
}
4294
}
4395
}
4496
}

0 commit comments

Comments
 (0)