Skip to content

Commit 1007f4b

Browse files
committed
validation: cleanup
- make constrainctvalidation cast safe - use config api for properties
1 parent 4b3ab1d commit 1007f4b

6 files changed

Lines changed: 256 additions & 194 deletions

File tree

modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
import java.time.Duration;
99
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;
10+
import java.util.*;
1411
import java.util.function.Consumer;
12+
import java.util.function.Function;
1513

14+
import com.typesafe.config.Config;
15+
import com.typesafe.config.ConfigValueType;
1616
import edu.umd.cs.findbugs.annotations.NonNull;
1717
import io.avaje.validation.ConstraintViolationException;
1818
import io.avaje.validation.Validator;
@@ -46,11 +46,11 @@
4646
* ConstraintViolationException} and transforms it into a {@link
4747
* io.jooby.validation.ValidationResult}
4848
*
49-
* @authors kliushnichenko, SentryMan
49+
* @author kliushnichenko
50+
* @author SentryMan
5051
* @since 3.3.1
5152
*/
5253
public class AvajeValidatorModule implements Extension {
53-
5454
private Consumer<Validator.Builder> configurer;
5555
private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY;
5656
private String title = "Validation failed";
@@ -105,43 +105,34 @@ public AvajeValidatorModule disableViolationHandler() {
105105
}
106106

107107
@Override
108-
public void install(@NonNull Jooby app) throws Exception {
109-
110-
var props = app.getEnvironment();
108+
public void install(@NonNull Jooby app) {
109+
var conf = app.getConfig();
111110

112-
final var locales = new ArrayList<Locale>();
113111
final var builder = Validator.builder();
114-
Optional.ofNullable(props.getProperty("validation.failFast", "false"))
115-
.map(Boolean::valueOf)
116-
.ifPresent(builder::failFast);
112+
withProperty(conf, "validation.failFast", conf::getBoolean).ifPresent(builder::failFast);
117113

118-
Optional.ofNullable(props.getProperty("validation.resourcebundle.names"))
119-
.map(s -> s.split(","))
120-
.ifPresent(builder::addResourceBundles);
121-
122-
Optional.ofNullable(props.getProperty("validation.locale.default"))
123-
.map(Locale::forLanguageTag)
114+
withProperty(conf, "validation.resourcebundle.names", path -> getStringList(conf, path))
115+
.ifPresent(values -> values.forEach(builder::addResourceBundles));
116+
// Locales from application
117+
Optional.ofNullable(app.getLocales())
124118
.ifPresent(
125-
l -> {
126-
builder.setDefaultLocale(l);
127-
locales.add(l);
119+
locales -> {
120+
builder.setDefaultLocale(locales.get(0));
121+
locales.stream().skip(1).forEach(builder::addLocales);
128122
});
129-
130-
Optional.ofNullable(props.getProperty("validation.locale.addedLocales")).stream()
131-
.flatMap(s -> Arrays.stream(s.split(",")))
123+
withProperty(conf, "validation.locale.default", conf::getString)
132124
.map(Locale::forLanguageTag)
133-
.forEach(
134-
l -> {
135-
builder.addLocales(l);
136-
locales.add(l);
137-
});
138-
139-
Optional.ofNullable(props.getProperty("validation.temporal.tolerance.value"))
140-
.map(Long::valueOf)
125+
.ifPresent(builder::setDefaultLocale);
126+
withProperty(conf, "validation.locale.addedLocales", path -> getStringList(conf, path))
127+
.orElseGet(List::of)
128+
.stream()
129+
.map(Locale::forLanguageTag)
130+
.forEach(builder::addLocales);
131+
withProperty(conf, "validation.temporal.tolerance.value", conf::getLong)
141132
.ifPresent(
142133
duration -> {
143-
final var unit =
144-
Optional.ofNullable(props.getProperty("validation.temporal.tolerance.chronoUnit"))
134+
var unit =
135+
withProperty(conf, "validation.temporal.tolerance.chronoUnit", conf::getString)
145136
.map(ChronoUnit::valueOf)
146137
.orElse(ChronoUnit.MILLIS);
147138
builder.temporalTolerance(Duration.of(duration, unit));
@@ -151,13 +142,12 @@ public void install(@NonNull Jooby app) throws Exception {
151142
configurer.accept(builder);
152143
}
153144

154-
Validator validator = builder.build();
145+
var validator = builder.build();
155146
app.getServices().put(Validator.class, validator);
156147
app.getServices().put(MvcValidator.class, new MvcValidatorImpl(validator));
157148

158149
if (!disableDefaultViolationHandler) {
159-
app.error(
160-
ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title));
150+
app.error(new ConstraintViolationHandler(statusCode, title));
161151
}
162152
}
163153

@@ -174,4 +164,17 @@ public void validate(Context ctx, Object bean) throws ConstraintViolationExcepti
174164
validator.validate(bean, ctx.locale());
175165
}
176166
}
167+
168+
private static <T> Optional<T> withProperty(Config config, String path, Function<String, T> fn) {
169+
if (config.hasPath(path)) {
170+
return Optional.of(fn.apply(path));
171+
}
172+
return Optional.empty();
173+
}
174+
175+
private static List<String> getStringList(Config config, String path) {
176+
return config.getValue(path).valueType() == ConfigValueType.STRING
177+
? List.of(config.getString(path))
178+
: config.getStringList(path);
179+
}
177180
}

modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby.avaje.validator;
27

38
import static io.jooby.validation.ValidationResult.ErrorType.FIELD;
@@ -49,36 +54,32 @@
4954
* @since 3.2.10
5055
*/
5156
public class ConstraintViolationHandler implements ErrorHandler {
52-
5357
private static final String ROOT_VIOLATIONS_PATH = "";
54-
5558
private final StatusCode statusCode;
5659
private final String title;
5760

58-
public ConstraintViolationHandler(StatusCode statusCode, String title) {
61+
public ConstraintViolationHandler(@NonNull StatusCode statusCode, @NonNull String title) {
5962
this.statusCode = statusCode;
6063
this.title = title;
6164
}
6265

6366
@Override
6467
public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
65-
var ex = (ConstraintViolationException) cause;
68+
if (cause instanceof ConstraintViolationException ex) {
69+
var violations = ex.violations();
6670

67-
var violations = ex.violations();
71+
var groupedByPath = violations.stream().collect(groupingBy(ConstraintViolation::path));
72+
var errors = collectErrors(groupedByPath);
6873

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);
74+
var result = new ValidationResult(title, statusCode.value(), errors);
75+
ctx.setResponseCode(statusCode).render(result);
76+
}
7677
}
7778

7879
private List<ValidationResult.Error> collectErrors(
7980
Map<String, List<ConstraintViolation>> groupedViolations) {
8081
List<ValidationResult.Error> errors = new ArrayList<>();
81-
for (Map.Entry<String, List<ConstraintViolation>> entry : groupedViolations.entrySet()) {
82+
for (var entry : groupedViolations.entrySet()) {
8283
var path = entry.getKey();
8384
if (ROOT_VIOLATIONS_PATH.equals(path)) {
8485
errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1+
/**
2+
* Avaje Validator Module: https://jooby.io/modules/avaje-validator.
3+
*
4+
* <pre>{@code
5+
* {
6+
* install(new AvajeValidatorModule());
7+
*
8+
* }
9+
*
10+
* public class Controller {
11+
*
12+
* @POST("/create")
13+
* public void create(@Valid Bean bean) {
14+
* }
15+
*
16+
* }
17+
* }</pre>
18+
*
19+
* <p>Supports validation of a single bean, list, array, or map.
20+
*
21+
* <p>The module also provides a built-in error handler that catches {@link
22+
* io.avaje.validation.ConstraintViolationException} and transforms it into a {@link
23+
* io.jooby.validation.ValidationResult}
24+
*/
125
package io.jooby.avaje.validator;
Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby.hibernate.validator;
27

8+
import static io.jooby.validation.ValidationResult.ErrorType.FIELD;
9+
import static io.jooby.validation.ValidationResult.ErrorType.GLOBAL;
10+
import static java.util.stream.Collectors.groupingBy;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Map;
15+
316
import edu.umd.cs.findbugs.annotations.NonNull;
417
import io.jooby.Context;
518
import io.jooby.ErrorHandler;
@@ -8,19 +21,11 @@
821
import jakarta.validation.ConstraintViolation;
922
import jakarta.validation.ConstraintViolationException;
1023

11-
import java.util.ArrayList;
12-
import java.util.List;
13-
import java.util.Map;
14-
import java.util.Set;
15-
16-
import static io.jooby.validation.ValidationResult.ErrorType.FIELD;
17-
import static io.jooby.validation.ValidationResult.ErrorType.GLOBAL;
18-
import static java.util.stream.Collectors.groupingBy;
19-
2024
/**
2125
* Catches and transform {@link ConstraintViolationException} into {@link ValidationResult}
22-
* <p>
23-
* Payload example:
26+
*
27+
* <p>Payload example:
28+
*
2429
* <pre>{@code
2530
* {
2631
* "title": "Validation failed",
@@ -50,45 +55,47 @@
5055
*/
5156
public class ConstraintViolationHandler implements ErrorHandler {
5257

53-
private static final String ROOT_VIOLATIONS_PATH = "";
58+
private static final String ROOT_VIOLATIONS_PATH = "";
5459

55-
private final StatusCode statusCode;
56-
private final String title;
60+
private final StatusCode statusCode;
61+
private final String title;
5762

58-
public ConstraintViolationHandler(StatusCode statusCode, String title) {
59-
this.statusCode = statusCode;
60-
this.title = title;
61-
}
63+
public ConstraintViolationHandler(@NonNull StatusCode statusCode, @NonNull String title) {
64+
this.statusCode = statusCode;
65+
this.title = title;
66+
}
6267

63-
@Override
64-
public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
65-
ConstraintViolationException ex = (ConstraintViolationException) cause;
68+
@Override
69+
public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
70+
if (cause instanceof ConstraintViolationException ex) {
71+
var violations = ex.getConstraintViolations();
6672

67-
Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
73+
var groupedByPath =
74+
violations.stream()
75+
.collect(groupingBy(violation -> violation.getPropertyPath().toString()));
6876

69-
Map<String, List<ConstraintViolation<?>>> groupedByPath = violations.stream()
70-
.collect(groupingBy(violation -> violation.getPropertyPath().toString()));
77+
var errors = collectErrors(groupedByPath);
7178

72-
List<ValidationResult.Error> errors = collectErrors(groupedByPath);
73-
74-
ValidationResult result = new ValidationResult(title, statusCode.value(), errors);
75-
ctx.setResponseCode(statusCode).render(result);
79+
var result = new ValidationResult(title, statusCode.value(), errors);
80+
ctx.setResponseCode(statusCode).render(result);
7681
}
82+
}
7783

78-
private List<ValidationResult.Error> collectErrors(Map<String, List<ConstraintViolation<?>>> groupedViolations) {
79-
List<ValidationResult.Error> errors = new ArrayList<>();
80-
for (Map.Entry<String, List<ConstraintViolation<?>>> entry : groupedViolations.entrySet()) {
81-
var path = entry.getKey();
82-
if (ROOT_VIOLATIONS_PATH.equals(path)) {
83-
errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
84-
} else {
85-
errors.add(new ValidationResult.Error(path, extractMessages(entry.getValue()), FIELD));
86-
}
87-
}
88-
return errors;
84+
private List<ValidationResult.Error> collectErrors(
85+
Map<String, List<ConstraintViolation<?>>> groupedViolations) {
86+
List<ValidationResult.Error> errors = new ArrayList<>();
87+
for (var entry : groupedViolations.entrySet()) {
88+
var path = entry.getKey();
89+
if (ROOT_VIOLATIONS_PATH.equals(path)) {
90+
errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
91+
} else {
92+
errors.add(new ValidationResult.Error(path, extractMessages(entry.getValue()), FIELD));
93+
}
8994
}
95+
return errors;
96+
}
9097

91-
private List<String> extractMessages(List<ConstraintViolation<?>> violations) {
92-
return violations.stream().map(ConstraintViolation::getMessage).toList();
93-
}
98+
private List<String> extractMessages(List<ConstraintViolation<?>> violations) {
99+
return violations.stream().map(ConstraintViolation::getMessage).toList();
100+
}
94101
}

0 commit comments

Comments
 (0)