Skip to content

Commit e8a8fb3

Browse files
authored
Merge pull request #58 from SentryMan/group
Add Group support
2 parents 72c9980 + cb99d1b commit e8a8fb3

20 files changed

+244
-108
lines changed

validator-generator/src/test/java/io/avaje/validation/generator/models/valid/CustomAnnotationAdapter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.validation.generator.models.valid;
22

33
import java.util.Map;
4+
import java.util.Set;
45

56
import io.avaje.lang.Nullable;
67
import io.avaje.validation.adapter.AnnotationValidator;
@@ -11,7 +12,7 @@
1112
@AnnotationValidator(Nullable.class)
1213
public final class CustomAnnotationAdapter implements ValidationAdapter<Object> {
1314

14-
public CustomAnnotationAdapter(ValidationContext ctx, Map<String, Object> attributes) {}
15+
public CustomAnnotationAdapter(ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes) {}
1516

1617
@Override
1718
public boolean validate(Object value, ValidationRequest req, String propertyName) {

validator/src/main/java/io/avaje/validation/Validator.java

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

3+
import io.avaje.lang.Nullable;
34
import io.avaje.validation.adapter.*;
45
import io.avaje.validation.core.DefaultBootstrap;
56
import io.avaje.validation.spi.Bootstrap;
@@ -12,12 +13,13 @@
1213
import java.util.Map;
1314
import java.util.ResourceBundle;
1415
import java.util.ServiceLoader;
16+
import java.util.Set;
1517
import java.util.function.Supplier;
1618

1719
public interface Validator {
1820

1921
/** Validate the object using the default locale. */
20-
void validate(Object any) throws ConstraintViolationException;
22+
void validate(Object value, @Nullable Class<?>... groups) throws ConstraintViolationException;
2123

2224
/**
2325
* Validate the object with a given locale.
@@ -26,7 +28,8 @@ public interface Validator {
2628
*
2729
* <p>This is expected to be used when the Validator is configured to support multiple locales.
2830
*/
29-
void validate(Object any, Locale locale) throws ConstraintViolationException;
31+
void validate(Object any, @Nullable Locale locale, @Nullable Class<?>... groups)
32+
throws ConstraintViolationException;
3033

3134
static Builder builder() {
3235
final var bootstrapService = ServiceLoader.load(Bootstrap.class).iterator();
@@ -52,9 +55,7 @@ interface Builder {
5255
*/
5356
Builder addResourceBundles(String... bundleName);
5457

55-
/**
56-
* Add ResourceBundle for error message interpolation
57-
*/
58+
/** Add ResourceBundle for error message interpolation */
5859
Builder addResourceBundles(ResourceBundle... bundle);
5960

6061
/** Set Default Locale for this validator, if not set, will use Locale.getDefault() */
@@ -63,10 +64,21 @@ interface Builder {
6364
/** Adds additional Locales for this validator */
6465
Builder addLocales(Locale... locales);
6566

67+
/**
68+
* Contract for obtaining the Clock used as the reference for now when validating the @Future
69+
* and @Past constraints.
70+
*/
6671
Builder clockProvider(Supplier<Clock> clockSupplier);
6772

73+
/** Define the acceptable margin of error when comparing date/time in temporal constraints. */
6874
Builder temporalTolerance(Duration temporalTolerance);
6975

76+
/**
77+
* En- or disables the fail fast mode. When fail fast is enabled the validation will stop on the
78+
* first constraint violation detected.
79+
*/
80+
Builder failFast(boolean failFast);
81+
7082
/** Add a AdapterBuilder which provides a ValidationAdapter to use for the given type. */
7183
Builder add(Type type, AdapterBuilder builder);
7284

@@ -101,7 +113,8 @@ interface AdapterBuilder {
101113
interface AnnotationAdapterBuilder {
102114

103115
/** Create a ValidationAdapter given the Validator instance. */
104-
ValidationAdapter<?> build(ValidationContext ctx, Map<String, Object> attributes);
116+
ValidationAdapter<?> build(
117+
ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes);
105118
}
106119

107120
/** Components register ValidationAdapters Validator.Builder */

validator/src/main/java/io/avaje/validation/adapter/AnnotationValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* private final Message message;
2121
*
22-
* public CustomAnnotationAdapter(ValidationContext ctx, Map<String, Object> attributes) {
22+
* public CustomAnnotationAdapter(ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes) {
2323
* //create a message object for error interpolation
2424
* message = ctx.message("{message.property}");
2525
*

validator/src/main/java/io/avaje/validation/adapter/ValidationAdapter.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package io.avaje.validation.adapter;
22

3+
import static java.util.stream.Collectors.toUnmodifiableSet;
4+
35
import java.util.Collection;
6+
import java.util.List;
47
import java.util.Map;
58
import java.util.Objects;
9+
import java.util.Set;
610

711
@FunctionalInterface
812
public interface ValidationAdapter<T> {
@@ -92,4 +96,21 @@ default ValidationAdapter<T> andThen(ValidationAdapter<? super T> after) {
9296
return true;
9397
};
9498
}
99+
100+
/**
101+
* Returns true if the validation request groups is empty or matches any of the adapter's
102+
* configured groups
103+
*/
104+
default boolean checkGroups(Set<Class<?>> adapterGroups, ValidationRequest request) {
105+
final var requestGroups = request.groups();
106+
107+
if (requestGroups.isEmpty()) return true;
108+
109+
for (final var group : requestGroups) {
110+
if (adapterGroups.contains(group)) {
111+
return true;
112+
}
113+
}
114+
return false;
115+
}
95116
}

validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.lang.annotation.Annotation;
44
import java.lang.reflect.Type;
55
import java.util.Map;
6+
import java.util.Set;
67

78
/**
89
* Context available when validation adapters are being created.
@@ -67,6 +68,6 @@ interface AnnotationFactory {
6768
* <p>Returning null means that the adapter could be created by another factory.
6869
*/
6970
ValidationAdapter<?> create(
70-
Class<? extends Annotation> annotationType, ValidationContext ctx, Map<String, Object> attributes);
71+
Class<? extends Annotation> annotationType, ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes);
7172
}
7273
}

validator/src/main/java/io/avaje/validation/adapter/ValidationRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package io.avaje.validation.adapter;
22

3+
import java.util.List;
4+
35
/**
46
* A validation request.
57
*/
68
public interface ValidationRequest {
79

10+
/** The groups tied to this ValidationRequest */
11+
List<Class<?>> groups();
12+
813
/**
914
* Add a constraint violation for the given property.
1015
*

validator/src/main/java/io/avaje/validation/core/CoreAdapterBuilder.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package io.avaje.validation.core;
22

3+
import static java.util.stream.Collectors.toUnmodifiableSet;
4+
35
import java.lang.annotation.Annotation;
46
import java.lang.reflect.Type;
57
import java.time.Clock;
68
import java.time.Duration;
79
import java.util.ArrayList;
810
import java.util.List;
911
import java.util.Map;
12+
import java.util.Set;
1013
import java.util.concurrent.ConcurrentHashMap;
1114
import java.util.function.Supplier;
1215

@@ -73,12 +76,18 @@ <T> ValidationAdapter<T> build(Type type, Object cacheKey) {
7376
<T> ValidationAdapter<T> buildAnnotation(Class<? extends Annotation> cls, Map<String, Object> attributes) {
7477
// Ask each factory to create the validation adapter.
7578
for (final ValidationContext.AnnotationFactory factory : annotationFactories) {
76-
final var result = (ValidationAdapter<T>) factory.create(cls, context, attributes);
79+
final var result = (ValidationAdapter<T>) factory.create(cls, context, extractGroups(attributes), attributes);
7780
if (result != null) {
7881
return result;
7982
}
8083
}
8184
// unknown annotations have noop
8285
return NOOP;
8386
}
87+
88+
static Set<Class<?>> extractGroups(Map<String, Object> attributes) {
89+
@SuppressWarnings("unchecked")
90+
final var annotationsGroups = (List<Class<?>>) attributes.getOrDefault("groups", List.of());
91+
return annotationsGroups.stream().collect(toUnmodifiableSet());
92+
}
8493
}

validator/src/main/java/io/avaje/validation/core/DRequest.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ final class DRequest implements ValidationRequest {
1515
private final Set<ConstraintViolation> violations = new LinkedHashSet<>();
1616

1717
private final DValidator validator;
18-
@Nullable
19-
private final Locale locale;
18+
private final boolean failfast;
19+
private final List<Class<?>> groups;
20+
@Nullable private final Locale locale;
2021

21-
DRequest(DValidator validator, @Nullable Locale locale) {
22+
DRequest(DValidator validator, boolean failfast, @Nullable Locale locale, List<Class<?>> groups) {
2223
this.validator = validator;
24+
this.failfast = failfast;
2325
this.locale = locale;
26+
this.groups = groups;
2427
}
2528

2629
private String currentPath() {
27-
StringJoiner joiner = new StringJoiner(".");
30+
final StringJoiner joiner = new StringJoiner(".");
2831
final var descendingIterator = pathStack.descendingIterator();
2932
while (descendingIterator.hasNext()) {
3033
joiner.add(descendingIterator.next());
@@ -34,8 +37,11 @@ private String currentPath() {
3437

3538
@Override
3639
public void addViolation(ValidationContext.Message msg, String propertyName) {
37-
String message = validator.interpolate(msg, locale);
40+
final String message = validator.interpolate(msg, locale);
3841
violations.add(new ConstraintViolation(currentPath(), propertyName, message));
42+
if (failfast) {
43+
throwWithViolations();
44+
}
3945
}
4046

4147
@Override
@@ -54,4 +60,9 @@ public void throwWithViolations() {
5460
throw new ConstraintViolationException(violations);
5561
}
5662
}
63+
64+
@Override
65+
public List<Class<?>> groups() {
66+
return groups;
67+
}
5768
}

validator/src/main/java/io/avaje/validation/core/DValidationType.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.validation.core;
22

3+
import java.util.List;
34
import java.util.Locale;
45

56
import io.avaje.validation.adapter.ValidationAdapter;
@@ -15,8 +16,8 @@ final class DValidationType<T> implements ValidationType<T> {
1516
}
1617

1718
@Override
18-
public void validate(T object, Locale locale) {
19-
final var req = validator.request(locale);
19+
public void validate(T object, Locale locale, List<Class<?>> groups) {
20+
final var req = validator.request(locale, groups);
2021
adapter.validate(object, req);
2122
req.throwWithViolations();
2223
}

validator/src/main/java/io/avaje/validation/core/DValidator.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.time.Clock;
1111
import java.time.Duration;
1212
import java.util.ArrayList;
13+
import java.util.Arrays;
1314
import java.util.Collections;
1415
import java.util.List;
1516
import java.util.Locale;
@@ -35,6 +36,7 @@ final class DValidator implements Validator, ValidationContext {
3536
private final LocaleResolver localeResolver;
3637
private final DTemplateLookup templateLookup;
3738
private final Map<String, String> messageCache = new ConcurrentHashMap<>();
39+
private final boolean failfast;
3840

3941
DValidator(
4042
List<AdapterFactory> factories,
@@ -44,7 +46,8 @@ final class DValidator implements Validator, ValidationContext {
4446
MessageInterpolator interpolator,
4547
LocaleResolver localeResolver,
4648
Supplier<Clock> clockSupplier,
47-
Duration temporalTolerance) {
49+
Duration temporalTolerance,
50+
boolean failfast) {
4851
this.localeResolver = localeResolver;
4952
final var defaultResourceBundle =
5053
new DResourceBundleManager(bundleNames, bundles, localeResolver);
@@ -53,22 +56,23 @@ final class DValidator implements Validator, ValidationContext {
5356
this.builder =
5457
new CoreAdapterBuilder(
5558
this, factories, annotationFactories, clockSupplier, temporalTolerance);
59+
this.failfast = failfast;
5660
}
5761

5862
MessageInterpolator interpolator() {
5963
return this.interpolator;
6064
}
6165

6266
@Override
63-
public void validate(Object any) {
64-
validate(any, null);
67+
public void validate(Object any, @Nullable Class<?>... groups) {
68+
validate(any, null, groups);
6569
}
6670

6771
@Override
6872
@SuppressWarnings("unchecked")
69-
public void validate(Object any, @Nullable Locale locale) {
73+
public void validate(Object any, @Nullable Locale locale, @Nullable Class<?>... groups) {
7074
final var type = (ValidationType<Object>) type(any.getClass());
71-
type.validate(any, locale);
75+
type.validate(any, locale, List.of(groups));
7276
}
7377

7478
private <T> ValidationType<T> type(Class<T> cls) {
@@ -119,8 +123,8 @@ public <T> ValidationAdapter<T> adapter(Type type) {
119123
return builder.build(type, cacheKey);
120124
}
121125

122-
ValidationRequest request(@Nullable Locale locale) {
123-
return new DRequest(this, locale);
126+
ValidationRequest request(@Nullable Locale locale, List<Class<?>> groups) {
127+
return new DRequest(this, failfast, locale, groups);
124128
}
125129

126130
String interpolate(Message msg, Locale requestLocale) {
@@ -147,6 +151,7 @@ static final class DBuilder implements Validator.Builder {
147151
private Locale defaultLocal = Locale.getDefault();
148152
private Supplier<Clock> clockSupplier = Clock::systemDefaultZone;
149153
private Duration temporalTolerance = Duration.ZERO;
154+
private boolean failfast;
150155

151156
@Override
152157
public Builder add(Type type, AdapterBuilder builder) {
@@ -222,6 +227,12 @@ public Builder temporalTolerance(Duration temporalTolerance) {
222227
return this;
223228
}
224229

230+
@Override
231+
public Builder failFast(boolean failfast) {
232+
this.failfast = failfast;
233+
return this;
234+
}
235+
225236
private void registerComponents() {
226237
// first register all user defined ValidatorComponent
227238
for (final ValidatorComponent next : ServiceLoader.load(ValidatorComponent.class)) {
@@ -249,13 +260,16 @@ public DValidator build() {
249260
interpolator,
250261
localeResolver,
251262
clockSupplier,
252-
temporalTolerance);
263+
temporalTolerance,
264+
failfast);
253265
}
254266

255-
private static <T> AnnotationFactory newAnnotationAdapterFactory(Type type, ValidationAdapter<T> adapter) {
267+
private static <T> AnnotationFactory newAnnotationAdapterFactory(
268+
Type type, ValidationAdapter<T> adapter) {
256269
requireNonNull(type);
257270
requireNonNull(adapter);
258-
return (targetType, context, attributes) -> simpleMatch(type, targetType) ? adapter : null;
271+
return (targetType, context, groups, attributes) ->
272+
simpleMatch(type, targetType) ? adapter : null;
259273
}
260274

261275
private static <T> AdapterFactory newAdapterFactory(Type type, ValidationAdapter<T> adapter) {
@@ -270,10 +284,12 @@ private static AdapterFactory newAdapterFactory(Type type, AdapterBuilder builde
270284
return (targetType, ctx) -> simpleMatch(type, targetType) ? builder.build(ctx) : null;
271285
}
272286

273-
private static AnnotationFactory newAdapterFactory(Class<? extends Annotation> type, AnnotationAdapterBuilder builder) {
287+
private static AnnotationFactory newAdapterFactory(
288+
Class<? extends Annotation> type, AnnotationAdapterBuilder builder) {
274289
requireNonNull(type);
275290
requireNonNull(builder);
276-
return (targetType, ctx, attributes) -> simpleMatch(type, targetType) ? builder.build(ctx, attributes) : null;
291+
return (targetType, ctx, groups, attributes) ->
292+
simpleMatch(type, targetType) ? builder.build(ctx, groups, attributes) : null;
277293
}
278294
}
279295

0 commit comments

Comments
 (0)