Skip to content

Commit eba2414

Browse files
committed
working
1 parent 22a0f5a commit eba2414

13 files changed

Lines changed: 810 additions & 6 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
6+
<parent>
7+
<groupId>io.jooby</groupId>
8+
<artifactId>modules</artifactId>
9+
<version>3.3.1-SNAPSHOT</version>
10+
</parent>
11+
12+
<modelVersion>4.0.0</modelVersion>
13+
<artifactId>jooby-avaje-validator</artifactId>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.jooby</groupId>
18+
<artifactId>jooby</artifactId>
19+
</dependency>
20+
21+
<dependency>
22+
<groupId>io.jooby</groupId>
23+
<artifactId>jooby-validation</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
26+
27+
<!-- Hibernate Validator -->
28+
<dependency>
29+
<groupId>io.avaje</groupId>
30+
<artifactId>avaje-validator</artifactId>
31+
</dependency>
32+
33+
<dependency>
34+
<groupId>jakarta.validation</groupId>
35+
<artifactId>jakarta.validation-api</artifactId>
36+
</dependency>
37+
38+
<!-- Test dependencies -->
39+
<dependency>
40+
<groupId>io.jooby</groupId>
41+
<artifactId>jooby-netty</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
45+
<dependency>
46+
<groupId>io.jooby</groupId>
47+
<artifactId>jooby-jackson</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
51+
<dependency>
52+
<groupId>org.junit.jupiter</groupId>
53+
<artifactId>junit-jupiter-api</artifactId>
54+
<scope>test</scope>
55+
</dependency>
56+
57+
<dependency>
58+
<groupId>org.junit.jupiter</groupId>
59+
<artifactId>junit-jupiter-engine</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
63+
<dependency>
64+
<groupId>io.jooby</groupId>
65+
<artifactId>jooby-test</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
69+
<dependency>
70+
<groupId>io.rest-assured</groupId>
71+
<artifactId>rest-assured</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>org.assertj</groupId>
77+
<artifactId>assertj-core</artifactId>
78+
<version>3.26.3</version>
79+
<scope>test</scope>
80+
</dependency>
81+
</dependencies>
82+
83+
<build>
84+
<plugins>
85+
<plugin>
86+
<groupId>org.apache.maven.plugins</groupId>
87+
<artifactId>maven-compiler-plugin</artifactId>
88+
<executions>
89+
<execution>
90+
<id>test</id>
91+
<phase>test-compile</phase>
92+
</execution>
93+
</executions>
94+
<configuration>
95+
<compilerArgs>
96+
<arg>-parameters</arg>
97+
</compilerArgs>
98+
<annotationProcessorPaths>
99+
<path>
100+
<groupId>io.jooby</groupId>
101+
<artifactId>jooby-apt</artifactId>
102+
</path>
103+
<path>
104+
<groupId>io.avaje</groupId>
105+
<artifactId>avaje-validator-generator</artifactId>
106+
</path>
107+
</annotationProcessorPaths>
108+
</configuration>
109+
</plugin>
110+
</plugins>
111+
</build>
112+
</project>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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.avaje.validator;
7+
8+
import java.time.Duration;
9+
import java.time.temporal.ChronoUnit;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.Locale;
13+
import java.util.Optional;
14+
import java.util.function.Consumer;
15+
16+
import edu.umd.cs.findbugs.annotations.NonNull;
17+
import io.avaje.validation.ConstraintViolationException;
18+
import io.avaje.validation.Validator;
19+
import io.jooby.Extension;
20+
import io.jooby.Jooby;
21+
import io.jooby.StatusCode;
22+
import io.jooby.validation.MvcValidator;
23+
24+
/**
25+
* Avaje Validator Module: https://jooby.io/modules/avaje-validator.
26+
*
27+
* <pre>{@code
28+
* {
29+
* install(new AvajeValidatorModule());
30+
*
31+
* }
32+
*
33+
* public class Controller {
34+
*
35+
* @POST("/create")
36+
* public void create(@Valid Bean bean) {
37+
* }
38+
*
39+
* }
40+
* }</pre>
41+
*
42+
* <p>Supports validation of a single bean, list, array, or map.
43+
*
44+
* <p>The module also provides a built-in error handler that catches {@link
45+
* ConstraintViolationException} and transforms it into a {@link
46+
* io.jooby.validation.ValidationResult}
47+
*
48+
* @authors kliushnichenko, SentryMan
49+
* @since 3.2.10
50+
*/
51+
public class AvajeValidatorModule implements Extension {
52+
53+
private Consumer<Validator.Builder> configurer;
54+
private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY;
55+
private String title = "Validation failed";
56+
private boolean disableDefaultViolationHandler = false;
57+
58+
/**
59+
* Setups a configurer callback.
60+
*
61+
* @param configurer Configurer callback.
62+
* @return This module.
63+
*/
64+
public AvajeValidatorModule doWith(@NonNull final Consumer<Validator.Builder> configurer) {
65+
this.configurer = configurer;
66+
return this;
67+
}
68+
69+
/**
70+
* Overrides the default status code for the errors produced by validation. Default code is
71+
* UNPROCESSABLE_ENTITY(422)
72+
*
73+
* @param statusCode new status code
74+
* @return This module.
75+
*/
76+
public AvajeValidatorModule statusCode(@NonNull StatusCode statusCode) {
77+
this.statusCode = statusCode;
78+
return this;
79+
}
80+
81+
/**
82+
* Overrides the default title for the errors produced by validation. Default title is "Validation
83+
* failed"
84+
*
85+
* @param title new title
86+
* @return This module.
87+
*/
88+
public AvajeValidatorModule validationTitle(@NonNull String title) {
89+
this.title = title;
90+
return this;
91+
}
92+
93+
/**
94+
* Disables default constraint violation handler. By default {@link AvajeValidatorModule} provides
95+
* built-in error handler for the {@link ConstraintViolationException} Such exceptions are
96+
* transformed into response of {@link io.jooby.validation.ValidationResult} Use this flag to
97+
* disable default error handler and provide your custom.
98+
*
99+
* @return This module.
100+
*/
101+
public AvajeValidatorModule disableViolationHandler() {
102+
this.disableDefaultViolationHandler = true;
103+
return this;
104+
}
105+
106+
@Override
107+
public void install(@NonNull Jooby app) throws Exception {
108+
109+
var props = app.getEnvironment();
110+
111+
final var locales = new ArrayList<Locale>();
112+
final var builder = Validator.builder();
113+
Optional.ofNullable(props.getProperty("validation.failFast", "false"))
114+
.map(Boolean::valueOf)
115+
.ifPresent(builder::failFast);
116+
117+
Optional.ofNullable(props.getProperty("validation.resourcebundle.names"))
118+
.map(s -> s.split(","))
119+
.ifPresent(builder::addResourceBundles);
120+
121+
Optional.ofNullable(props.getProperty("validation.locale.default"))
122+
.map(Locale::forLanguageTag)
123+
.ifPresent(
124+
l -> {
125+
builder.setDefaultLocale(l);
126+
locales.add(l);
127+
});
128+
129+
Optional.ofNullable(props.getProperty("validation.locale.addedLocales")).stream()
130+
.flatMap(s -> Arrays.stream(s.split(",")))
131+
.map(Locale::forLanguageTag)
132+
.forEach(
133+
l -> {
134+
builder.addLocales(l);
135+
locales.add(l);
136+
});
137+
138+
Optional.ofNullable(props.getProperty("validation.temporal.tolerance.value"))
139+
.map(Long::valueOf)
140+
.ifPresent(
141+
duration -> {
142+
final var unit =
143+
Optional.ofNullable(props.getProperty("validation.temporal.tolerance.chronoUnit"))
144+
.map(ChronoUnit::valueOf)
145+
.orElse(ChronoUnit.MILLIS);
146+
builder.temporalTolerance(Duration.of(duration, unit));
147+
});
148+
149+
if (configurer != null) {
150+
configurer.accept(builder);
151+
}
152+
153+
Validator validator = builder.build();
154+
app.getServices().put(Validator.class, validator);
155+
app.getServices().put(MvcValidator.class, new MvcValidatorImpl(validator));
156+
157+
if (!disableDefaultViolationHandler) {
158+
app.error(
159+
ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title));
160+
}
161+
}
162+
163+
static class MvcValidatorImpl implements MvcValidator {
164+
165+
private final Validator validator;
166+
167+
MvcValidatorImpl(Validator validator) {
168+
this.validator = validator;
169+
}
170+
171+
@Override
172+
public void validate(Object bean) throws ConstraintViolationException {
173+
validator.validate(bean);
174+
}
175+
}
176+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package io.jooby.avaje.validator;
2+
3+
import static io.jooby.validation.ValidationResult.ErrorType.FIELD;
4+
import static io.jooby.validation.ValidationResult.ErrorType.GLOBAL;
5+
import static java.util.stream.Collectors.groupingBy;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import edu.umd.cs.findbugs.annotations.NonNull;
12+
import io.avaje.validation.ConstraintViolation;
13+
import io.avaje.validation.ConstraintViolationException;
14+
import io.jooby.Context;
15+
import io.jooby.ErrorHandler;
16+
import io.jooby.StatusCode;
17+
import io.jooby.validation.ValidationResult;
18+
19+
/**
20+
* Catches and transform {@link ConstraintViolationException} into {@link ValidationResult}
21+
*
22+
* <p>Payload example:
23+
*
24+
* <pre>{@code
25+
* {
26+
* "title": "Validation failed",
27+
* "status": 422,
28+
* "errors": [
29+
* {
30+
* "field": null,
31+
* "messages": [
32+
* "Passwords should match"
33+
* ],
34+
* "type": "GLOBAL"
35+
* },
36+
* {
37+
* "field": "firstName",
38+
* "messages": [
39+
* "must not be empty",
40+
* "must not be null"
41+
* ],
42+
* "type": "FIELD"
43+
* }
44+
* ]
45+
* }
46+
* }</pre>
47+
*
48+
* @author kliushnichenko
49+
* @since 3.2.10
50+
*/
51+
public class ConstraintViolationHandler implements ErrorHandler {
52+
53+
private static final String ROOT_VIOLATIONS_PATH = "";
54+
55+
private final StatusCode statusCode;
56+
private final String title;
57+
58+
public ConstraintViolationHandler(StatusCode statusCode, String title) {
59+
this.statusCode = statusCode;
60+
this.title = title;
61+
}
62+
63+
@Override
64+
public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
65+
var ex = (ConstraintViolationException) cause;
66+
67+
var violations = ex.violations();
68+
69+
Map<String, List<ConstraintViolation>> groupedByPath =
70+
violations.stream().collect(groupingBy(violation -> violation.path().toString()));
71+
72+
List<ValidationResult.Error> errors = collectErrors(groupedByPath);
73+
74+
ValidationResult result = new ValidationResult(title, statusCode.value(), errors);
75+
ctx.setResponseCode(statusCode).render(result);
76+
}
77+
78+
private List<ValidationResult.Error> collectErrors(
79+
Map<String, List<ConstraintViolation>> groupedViolations) {
80+
List<ValidationResult.Error> errors = new ArrayList<>();
81+
for (Map.Entry<String, List<ConstraintViolation>> entry : groupedViolations.entrySet()) {
82+
var path = entry.getKey();
83+
if (ROOT_VIOLATIONS_PATH.equals(path)) {
84+
errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
85+
} else {
86+
errors.add(new ValidationResult.Error(path, extractMessages(entry.getValue()), FIELD));
87+
}
88+
}
89+
return errors;
90+
}
91+
92+
private List<String> extractMessages(List<ConstraintViolation> violations) {
93+
return violations.stream().map(ConstraintViolation::message).toList();
94+
}
95+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package io.jooby.avaje.validator;

0 commit comments

Comments
 (0)