Skip to content
This repository was archived by the owner on Mar 30, 2020. It is now read-only.

Commit 32868f5

Browse files
Merge pull request #2 from victools/validation-groups
Support javax.validation groups
2 parents 1749e1a + c86842f commit 32868f5

File tree

3 files changed

+363
-103
lines changed

3 files changed

+363
-103
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Option for returning "idn-email" instead of "email" as "format" if `@Email` is present
1313
- Indicate a string's "pattern" according to regular expressions on `@Pattern` or `@Email` (ignoring specified flags)
1414
- Option for enabling the inclusion of "pattern" expressions (they are excluded by default)
15+
- Allow filtering applicable annotations by their declared validation `groups` via `JavaxValidationModule.forValidationGroups()`
1516

1617
## [3.0.0] – 2019-06-10
1718
### Added

src/main/java/com/github/victools/jsonschema/module/javax/validation/JavaxValidationModule.java

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import java.math.BigDecimal;
2727
import java.util.Arrays;
2828
import java.util.Collections;
29-
import java.util.List;
29+
import java.util.HashSet;
30+
import java.util.Set;
31+
import java.util.function.Function;
3032
import javax.validation.constraints.DecimalMax;
3133
import javax.validation.constraints.DecimalMin;
3234
import javax.validation.constraints.Email;
@@ -56,15 +58,38 @@
5658
*/
5759
public class JavaxValidationModule implements Module {
5860

59-
private final List<JavaxValidationOption> options;
61+
private final Set<JavaxValidationOption> options;
62+
private Set<Class<?>> validationGroups;
6063

6164
/**
6265
* Constructor.
6366
*
6467
* @param options features to enable
6568
*/
6669
public JavaxValidationModule(JavaxValidationOption... options) {
67-
this.options = options == null ? Collections.emptyList() : Arrays.asList(options);
70+
this.options = options == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(options));
71+
// by default: ignore validation groups
72+
this.validationGroups = null;
73+
}
74+
75+
/**
76+
* Add validation groups to be considered.
77+
* <ul>
78+
* <li>Never calling this method will result in all annotations to be picked-up.</li>
79+
* <li>Calling this without parameters will only consider those annotations where no groups are defined.</li>
80+
* <li>Calling this with not-null parameters will only consider those annotations without defined groups or where at least one matches.</li>
81+
* </ul>
82+
*
83+
* @param validationGroups validation groups to consider
84+
* @return this module instance (for chaining)
85+
*/
86+
public JavaxValidationModule forValidationGroups(Class<?>... validationGroups) {
87+
if (validationGroups == null) {
88+
this.validationGroups = null;
89+
} else {
90+
this.validationGroups = new HashSet<>(Arrays.asList(validationGroups));
91+
}
92+
return this;
6893
}
6994

7095
@Override
@@ -110,12 +135,14 @@ private void applyToConfigPart(SchemaGeneratorConfigPart<?> configPart) {
110135
* @param <A> type of annotation
111136
* @param member field or method to retrieve annotation instance from (or from a field's getter or getter method's field)
112137
* @param annotationClass type of annotation
138+
* @param validationGroupsLookup how to look-up the associated validation groups of an annotation instance
113139
* @return annotation instance (or {@code null})
114140
* @see MemberScope#getAnnotation(Class)
115141
* @see FieldScope#findGetter()
116142
* @see MethodScope#findGetterField()
117143
*/
118-
protected <A extends Annotation> A getAnnotationFromFieldOrGetter(MemberScope<?, ?> member, Class<A> annotationClass) {
144+
protected <A extends Annotation> A getAnnotationFromFieldOrGetter(MemberScope<?, ?> member, Class<A> annotationClass,
145+
Function<A, Class<?>[]> validationGroupsLookup) {
119146
A annotation = member.getAnnotation(annotationClass);
120147
if (annotation == null) {
121148
MemberScope<?, ?> associatedGetterOrField;
@@ -128,6 +155,20 @@ protected <A extends Annotation> A getAnnotationFromFieldOrGetter(MemberScope<?,
128155
}
129156
annotation = associatedGetterOrField == null ? null : associatedGetterOrField.getAnnotation(annotationClass);
130157
}
158+
if (annotation != null) {
159+
Class<?>[] associatedGroups = validationGroupsLookup.apply(annotation);
160+
/*
161+
* the annotation is deemed applicable in one of the following three cases:
162+
* 1. Validation groups are specifically ignored (i.e. forValidationGroups() was never called or with null as only parameter)
163+
* 2. No validation groups are specified on the annotation.
164+
* 3. Some validation group(s) are specified on the annotation and at least one of them was provided via forValidationGroups().
165+
*/
166+
if (this.validationGroups != null && associatedGroups.length > 0
167+
&& Collections.disjoint(this.validationGroups, Arrays.asList(associatedGroups))) {
168+
// ignore the looked-up annotation as it is not associated with one of the desired validation groups
169+
annotation = null;
170+
}
171+
}
131172
return annotation;
132173
}
133174

@@ -139,12 +180,12 @@ protected <A extends Annotation> A getAnnotationFromFieldOrGetter(MemberScope<?,
139180
*/
140181
protected Boolean isNullable(MemberScope<?, ?> member) {
141182
Boolean result;
142-
if (this.getAnnotationFromFieldOrGetter(member, NotNull.class) != null
143-
|| this.getAnnotationFromFieldOrGetter(member, NotBlank.class) != null
144-
|| this.getAnnotationFromFieldOrGetter(member, NotEmpty.class) != null) {
183+
if (this.getAnnotationFromFieldOrGetter(member, NotNull.class, NotNull::groups) != null
184+
|| this.getAnnotationFromFieldOrGetter(member, NotBlank.class, NotBlank::groups) != null
185+
|| this.getAnnotationFromFieldOrGetter(member, NotEmpty.class, NotEmpty::groups) != null) {
145186
// field is specifically NOT nullable
146187
result = Boolean.FALSE;
147-
} else if (this.getAnnotationFromFieldOrGetter(member, Null.class) != null) {
188+
} else if (this.getAnnotationFromFieldOrGetter(member, Null.class, Null::groups) != null) {
148189
// field is specifically null (and thereby nullable)
149190
result = Boolean.TRUE;
150191
} else {
@@ -173,12 +214,12 @@ protected boolean isRequired(MemberScope<?, ?> member) {
173214
*/
174215
protected Integer resolveArrayMinItems(MemberScope<?, ?> member) {
175216
if (member.isContainerType()) {
176-
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class);
217+
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class, Size::groups);
177218
if (sizeAnnotation != null && sizeAnnotation.min() > 0) {
178219
// minimum length greater than the default 0 was specified
179220
return sizeAnnotation.min();
180221
}
181-
if (this.getAnnotationFromFieldOrGetter(member, NotEmpty.class) != null) {
222+
if (this.getAnnotationFromFieldOrGetter(member, NotEmpty.class, NotEmpty::groups) != null) {
182223
return 1;
183224
}
184225
}
@@ -194,7 +235,7 @@ protected Integer resolveArrayMinItems(MemberScope<?, ?> member) {
194235
*/
195236
protected Integer resolveArrayMaxItems(MemberScope<?, ?> member) {
196237
if (member.isContainerType()) {
197-
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class);
238+
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class, Size::groups);
198239
if (sizeAnnotation != null && sizeAnnotation.max() < 2147483647) {
199240
// maximum length below the default 2147483647 was specified
200241
return sizeAnnotation.max();
@@ -214,13 +255,13 @@ protected Integer resolveArrayMaxItems(MemberScope<?, ?> member) {
214255
*/
215256
protected Integer resolveStringMinLength(MemberScope<?, ?> member) {
216257
if (member.getType().isInstanceOf(CharSequence.class)) {
217-
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class);
258+
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class, Size::groups);
218259
if (sizeAnnotation != null && sizeAnnotation.min() > 0) {
219260
// minimum length greater than the default 0 was specified
220261
return sizeAnnotation.min();
221262
}
222-
if (this.getAnnotationFromFieldOrGetter(member, NotEmpty.class) != null
223-
|| this.getAnnotationFromFieldOrGetter(member, NotBlank.class) != null) {
263+
if (this.getAnnotationFromFieldOrGetter(member, NotEmpty.class, NotEmpty::groups) != null
264+
|| this.getAnnotationFromFieldOrGetter(member, NotBlank.class, NotBlank::groups) != null) {
224265
return 1;
225266
}
226267
}
@@ -236,7 +277,7 @@ protected Integer resolveStringMinLength(MemberScope<?, ?> member) {
236277
*/
237278
protected Integer resolveStringMaxLength(MemberScope<?, ?> member) {
238279
if (member.getType().isInstanceOf(CharSequence.class)) {
239-
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class);
280+
Size sizeAnnotation = this.getAnnotationFromFieldOrGetter(member, Size.class, Size::groups);
240281
if (sizeAnnotation != null && sizeAnnotation.max() < 2147483647) {
241282
// maximum length below the default 2147483647 was specified
242283
return sizeAnnotation.max();
@@ -254,7 +295,7 @@ protected Integer resolveStringMaxLength(MemberScope<?, ?> member) {
254295
*/
255296
protected String resolveStringFormat(MemberScope<?, ?> member) {
256297
if (member.getType().isInstanceOf(CharSequence.class)) {
257-
Email emailAnnotation = this.getAnnotationFromFieldOrGetter(member, Email.class);
298+
Email emailAnnotation = this.getAnnotationFromFieldOrGetter(member, Email.class, Email::groups);
258299
if (emailAnnotation != null) {
259300
// @Email annotation was found, indicate the respective format
260301
if (this.options.contains(JavaxValidationOption.PREFER_IDN_EMAIL_FORMAT)) {
@@ -277,12 +318,12 @@ protected String resolveStringFormat(MemberScope<?, ?> member) {
277318
*/
278319
protected String resolveStringPattern(MemberScope<?, ?> member) {
279320
if (member.getType().isInstanceOf(CharSequence.class)) {
280-
Pattern patternAnnotation = this.getAnnotationFromFieldOrGetter(member, Pattern.class);
321+
Pattern patternAnnotation = this.getAnnotationFromFieldOrGetter(member, Pattern.class, Pattern::groups);
281322
if (patternAnnotation != null) {
282323
// @Pattern annotation was found, return its (mandatory) regular expression
283324
return patternAnnotation.regexp();
284325
}
285-
Email emailAnnotation = this.getAnnotationFromFieldOrGetter(member, Email.class);
326+
Email emailAnnotation = this.getAnnotationFromFieldOrGetter(member, Email.class, Email::groups);
286327
if (emailAnnotation != null && !".*".equals(emailAnnotation.regexp())) {
287328
// non-default regular expression on @Email annotation should also be considered
288329
return emailAnnotation.regexp();
@@ -301,15 +342,15 @@ protected String resolveStringPattern(MemberScope<?, ?> member) {
301342
* @see PositiveOrZero
302343
*/
303344
protected BigDecimal resolveNumberInclusiveMinimum(MemberScope<?, ?> member) {
304-
Min minAnnotation = this.getAnnotationFromFieldOrGetter(member, Min.class);
345+
Min minAnnotation = this.getAnnotationFromFieldOrGetter(member, Min.class, Min::groups);
305346
if (minAnnotation != null) {
306347
return new BigDecimal(minAnnotation.value());
307348
}
308-
DecimalMin decimalMinAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMin.class);
349+
DecimalMin decimalMinAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMin.class, DecimalMin::groups);
309350
if (decimalMinAnnotation != null && decimalMinAnnotation.inclusive()) {
310351
return new BigDecimal(decimalMinAnnotation.value());
311352
}
312-
PositiveOrZero positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, PositiveOrZero.class);
353+
PositiveOrZero positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, PositiveOrZero.class, PositiveOrZero::groups);
313354
if (positiveAnnotation != null) {
314355
return BigDecimal.ZERO;
315356
}
@@ -325,11 +366,11 @@ protected BigDecimal resolveNumberInclusiveMinimum(MemberScope<?, ?> member) {
325366
* @see Positive
326367
*/
327368
protected BigDecimal resolveNumberExclusiveMinimum(MemberScope<?, ?> member) {
328-
DecimalMin decimalMinAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMin.class);
369+
DecimalMin decimalMinAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMin.class, DecimalMin::groups);
329370
if (decimalMinAnnotation != null && !decimalMinAnnotation.inclusive()) {
330371
return new BigDecimal(decimalMinAnnotation.value());
331372
}
332-
Positive positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, Positive.class);
373+
Positive positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, Positive.class, Positive::groups);
333374
if (positiveAnnotation != null) {
334375
return BigDecimal.ZERO;
335376
}
@@ -346,15 +387,15 @@ protected BigDecimal resolveNumberExclusiveMinimum(MemberScope<?, ?> member) {
346387
* @see NegativeOrZero
347388
*/
348389
protected BigDecimal resolveNumberInclusiveMaximum(MemberScope<?, ?> member) {
349-
Max maxAnnotation = this.getAnnotationFromFieldOrGetter(member, Max.class);
390+
Max maxAnnotation = this.getAnnotationFromFieldOrGetter(member, Max.class, Max::groups);
350391
if (maxAnnotation != null) {
351392
return new BigDecimal(maxAnnotation.value());
352393
}
353-
DecimalMax decimalMaxAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMax.class);
394+
DecimalMax decimalMaxAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMax.class, DecimalMax::groups);
354395
if (decimalMaxAnnotation != null && decimalMaxAnnotation.inclusive()) {
355396
return new BigDecimal(decimalMaxAnnotation.value());
356397
}
357-
NegativeOrZero negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, NegativeOrZero.class);
398+
NegativeOrZero negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, NegativeOrZero.class, NegativeOrZero::groups);
358399
if (negativeAnnotation != null) {
359400
return BigDecimal.ZERO;
360401
}
@@ -370,11 +411,11 @@ protected BigDecimal resolveNumberInclusiveMaximum(MemberScope<?, ?> member) {
370411
* @see Negative
371412
*/
372413
protected BigDecimal resolveNumberExclusiveMaximum(MemberScope<?, ?> member) {
373-
DecimalMax decimalMaxAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMax.class);
414+
DecimalMax decimalMaxAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMax.class, DecimalMax::groups);
374415
if (decimalMaxAnnotation != null && !decimalMaxAnnotation.inclusive()) {
375416
return new BigDecimal(decimalMaxAnnotation.value());
376417
}
377-
Negative negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, Negative.class);
418+
Negative negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, Negative.class, Negative::groups);
378419
if (negativeAnnotation != null) {
379420
return BigDecimal.ZERO;
380421
}

0 commit comments

Comments
 (0)