16
16
17
17
package com .github .victools .jsonschema .module .javax .validation ;
18
18
19
- import com .github .victools .jsonschema .generator .JavaType ;
19
+ import com .github .victools .jsonschema .generator .FieldScope ;
20
+ import com .github .victools .jsonschema .generator .MemberScope ;
21
+ import com .github .victools .jsonschema .generator .MethodScope ;
20
22
import com .github .victools .jsonschema .generator .Module ;
21
23
import com .github .victools .jsonschema .generator .SchemaGeneratorConfigBuilder ;
22
24
import com .github .victools .jsonschema .generator .SchemaGeneratorConfigPart ;
23
- import com .github .victools .jsonschema .generator .impl .ReflectionTypeUtils ;
24
- import java .lang .reflect .AccessibleObject ;
25
+ import java .lang .annotation .Annotation ;
25
26
import java .math .BigDecimal ;
26
27
import javax .validation .constraints .DecimalMax ;
27
28
import javax .validation .constraints .DecimalMin ;
38
39
import javax .validation .constraints .Size ;
39
40
40
41
/**
41
- * With this module, the base assumption for the nullable indication is that all fields and methods are nullable unless annotated otherwise.
42
+ * JSON Schema Generation Module: based on annotations from the {@code javax.validation.constraints} package.
43
+ * <ul>
44
+ * <li>Determine whether a member is not nullable, base assumption being that all fields and method return values are nullable if not annotated.</li>
45
+ * <li>Populate "minItems" and "maxItems" for containers (i.e. arrays and collections).</li>
46
+ * <li>Populate "minLength" and "maxLength" for strings.</li>
47
+ * <li>Populate "minimum"/"exclusiveMinimum" and "maximum"/"exclusiveMaximum" for numbers.</li>
48
+ * </ul>
42
49
*/
43
50
public class JavaxValidationModule implements Module {
44
51
@@ -53,7 +60,7 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
53
60
*
54
61
* @param configPart config builder part to add configurations to
55
62
*/
56
- private void applyToConfigPart (SchemaGeneratorConfigPart <? extends AccessibleObject > configPart ) {
63
+ private void applyToConfigPart (SchemaGeneratorConfigPart <?> configPart ) {
57
64
configPart .withNullableCheck (this ::isNullable );
58
65
configPart .withArrayMinItemsResolver (this ::resolveArrayMinItems );
59
66
configPart .withArrayMaxItemsResolver (this ::resolveArrayMaxItems );
@@ -65,21 +72,47 @@ private void applyToConfigPart(SchemaGeneratorConfigPart<? extends AccessibleObj
65
72
configPart .withNumberExclusiveMaximumResolver (this ::resolveNumberExclusiveMaximum );
66
73
}
67
74
75
+ /**
76
+ * Retrieves the annotation instance of the given type, either from the field it self or (if not present) from its getter.
77
+ *
78
+ * @param <A> type of annotation
79
+ * @param member field or method to retrieve annotation instance from (or from a field's getter or getter method's field)
80
+ * @param annotationClass type of annotation
81
+ * @return annotation instance (or {@code null})
82
+ * @see MemberScope#getAnnotation(Class)
83
+ * @see FieldScope#findGetter()
84
+ * @see MethodScope#findGetterField()
85
+ */
86
+ protected <A extends Annotation > A getAnnotationFromFieldOrGetter (MemberScope <?, ?> member , Class <A > annotationClass ) {
87
+ A annotation = member .getAnnotation (annotationClass );
88
+ if (annotation == null ) {
89
+ MemberScope <?, ?> associatedGetterOrField ;
90
+ if (member instanceof FieldScope ) {
91
+ associatedGetterOrField = ((FieldScope ) member ).findGetter ();
92
+ } else if (member instanceof MethodScope ) {
93
+ associatedGetterOrField = ((MethodScope ) member ).findGetterField ();
94
+ } else {
95
+ associatedGetterOrField = null ;
96
+ }
97
+ annotation = associatedGetterOrField == null ? null : associatedGetterOrField .getAnnotation (annotationClass );
98
+ }
99
+ return annotation ;
100
+ }
101
+
68
102
/**
69
103
* Determine whether a given field or method is annotated to be not nullable.
70
104
*
71
- * @param fieldOrMethod the field or method to check
72
- * @param type field's or method return value's type
73
- * @return whether the field/method is specifically annotated as nullable or not (returns null if not specified: assumption it is nullable then)
105
+ * @param member the field or method to check
106
+ * @return whether member is annotated as nullable or not (returns null if not specified: assumption it is nullable then)
74
107
*/
75
- private Boolean isNullable (AccessibleObject fieldOrMethod , JavaType type ) {
108
+ protected Boolean isNullable (MemberScope <?, ?> member ) {
76
109
Boolean result ;
77
- if (fieldOrMethod . isAnnotationPresent ( NotNull .class )
78
- || fieldOrMethod . isAnnotationPresent ( NotBlank .class )
79
- || fieldOrMethod . isAnnotationPresent ( NotEmpty .class )) {
110
+ if (this . getAnnotationFromFieldOrGetter ( member , NotNull .class ) != null
111
+ || this . getAnnotationFromFieldOrGetter ( member , NotBlank .class ) != null
112
+ || this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null ) {
80
113
// field is specifically NOT nullable
81
114
result = Boolean .FALSE ;
82
- } else if (fieldOrMethod . isAnnotationPresent ( Null .class )) {
115
+ } else if (this . getAnnotationFromFieldOrGetter ( member , Null .class ) != null ) {
83
116
// field is specifically null (and thereby nullable)
84
117
result = Boolean .TRUE ;
85
118
} else {
@@ -91,19 +124,18 @@ private Boolean isNullable(AccessibleObject fieldOrMethod, JavaType type) {
91
124
/**
92
125
* Determine a given array type's minimum number of items.
93
126
*
94
- * @param fieldOrMethod the field or method to check
95
- * @param type field's or method return value's type
127
+ * @param member the field or method to check
96
128
* @return specified minimum number of array items (or null)
97
129
* @see Size
98
130
*/
99
- private Integer resolveArrayMinItems (AccessibleObject fieldOrMethod , JavaType type ) {
100
- if (ReflectionTypeUtils . isArrayType ( type )) {
101
- Size sizeAnnotation = fieldOrMethod . getAnnotation ( Size .class );
131
+ protected Integer resolveArrayMinItems (MemberScope <?, ?> member ) {
132
+ if (member . isContainerType ( )) {
133
+ Size sizeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Size .class );
102
134
if (sizeAnnotation != null && sizeAnnotation .min () > 0 ) {
103
135
// minimum length greater than the default 0 was specified
104
136
return sizeAnnotation .min ();
105
137
}
106
- if (fieldOrMethod . isAnnotationPresent ( NotEmpty .class )) {
138
+ if (this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null ) {
107
139
return 1 ;
108
140
}
109
141
}
@@ -113,14 +145,13 @@ private Integer resolveArrayMinItems(AccessibleObject fieldOrMethod, JavaType ty
113
145
/**
114
146
* Determine a given array type's maximum number of items.
115
147
*
116
- * @param fieldOrMethod the field or method to check
117
- * @param type field's or method return value's type
148
+ * @param member the field or method to check
118
149
* @return specified maximum number of array items (or null)
119
150
* @see Size
120
151
*/
121
- private Integer resolveArrayMaxItems (AccessibleObject fieldOrMethod , JavaType type ) {
122
- if (ReflectionTypeUtils . isArrayType ( type )) {
123
- Size sizeAnnotation = fieldOrMethod . getAnnotation ( Size .class );
152
+ protected Integer resolveArrayMaxItems (MemberScope <?, ?> member ) {
153
+ if (member . isContainerType ( )) {
154
+ Size sizeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Size .class );
124
155
if (sizeAnnotation != null && sizeAnnotation .max () < 2147483647 ) {
125
156
// maximum length below the default 2147483647 was specified
126
157
return sizeAnnotation .max ();
@@ -132,23 +163,21 @@ private Integer resolveArrayMaxItems(AccessibleObject fieldOrMethod, JavaType ty
132
163
/**
133
164
* Determine a given text type's minimum number of characters.
134
165
*
135
- * @param fieldOrMethod the field or method to check
136
- * @param type field's or method return value's type
166
+ * @param member the field or method to check
137
167
* @return specified minimum number of characters (or null)
138
168
* @see Size
139
169
* @see NotEmpty
140
170
* @see NotBlank
141
171
*/
142
- private Integer resolveStringMinLength (AccessibleObject fieldOrMethod , JavaType type ) {
143
- Class <?> rawType = ReflectionTypeUtils .getRawType (type .getResolvedType ());
144
- if (CharSequence .class .isAssignableFrom (rawType )) {
145
- Size sizeAnnotation = fieldOrMethod .getAnnotation (Size .class );
172
+ protected Integer resolveStringMinLength (MemberScope <?, ?> member ) {
173
+ if (member .getType ().isInstanceOf (CharSequence .class )) {
174
+ Size sizeAnnotation = this .getAnnotationFromFieldOrGetter (member , Size .class );
146
175
if (sizeAnnotation != null && sizeAnnotation .min () > 0 ) {
147
176
// minimum length greater than the default 0 was specified
148
177
return sizeAnnotation .min ();
149
178
}
150
- if (fieldOrMethod . isAnnotationPresent ( NotEmpty .class )
151
- || fieldOrMethod . isAnnotationPresent ( NotBlank .class )) {
179
+ if (this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null
180
+ || this . getAnnotationFromFieldOrGetter ( member , NotBlank .class ) != null ) {
152
181
return 1 ;
153
182
}
154
183
}
@@ -158,15 +187,13 @@ private Integer resolveStringMinLength(AccessibleObject fieldOrMethod, JavaType
158
187
/**
159
188
* Determine a given text type's maximum number of characters.
160
189
*
161
- * @param fieldOrMethod the field or method to check
162
- * @param type field's or method return value's type
190
+ * @param member the field or method to check
163
191
* @return specified minimum number of characters (or null)
164
192
* @see Size
165
193
*/
166
- private Integer resolveStringMaxLength (AccessibleObject fieldOrMethod , JavaType type ) {
167
- Class <?> rawType = ReflectionTypeUtils .getRawType (type .getResolvedType ());
168
- if (CharSequence .class .isAssignableFrom (rawType )) {
169
- Size sizeAnnotation = fieldOrMethod .getAnnotation (Size .class );
194
+ protected Integer resolveStringMaxLength (MemberScope <?, ?> member ) {
195
+ if (member .getType ().isInstanceOf (CharSequence .class )) {
196
+ Size sizeAnnotation = this .getAnnotationFromFieldOrGetter (member , Size .class );
170
197
if (sizeAnnotation != null && sizeAnnotation .max () < 2147483647 ) {
171
198
// maximum length below the default 2147483647 was specified
172
199
return sizeAnnotation .max ();
@@ -178,23 +205,22 @@ private Integer resolveStringMaxLength(AccessibleObject fieldOrMethod, JavaType
178
205
/**
179
206
* Determine a number type's minimum (inclusive) value.
180
207
*
181
- * @param fieldOrMethod the field or method to check
182
- * @param type field's or method return value's type
208
+ * @param member the field or method to check
183
209
* @return specified inclusive minimum value (or null)
184
210
* @see Min
185
211
* @see DecimalMin
186
212
* @see PositiveOrZero
187
213
*/
188
- private BigDecimal resolveNumberInclusiveMinimum (AccessibleObject fieldOrMethod , JavaType type ) {
189
- Min minAnnotation = fieldOrMethod . getAnnotation ( Min .class );
214
+ protected BigDecimal resolveNumberInclusiveMinimum (MemberScope <?, ?> member ) {
215
+ Min minAnnotation = this . getAnnotationFromFieldOrGetter ( member , Min .class );
190
216
if (minAnnotation != null ) {
191
217
return new BigDecimal (minAnnotation .value ());
192
218
}
193
- DecimalMin decimalMinAnnotation = fieldOrMethod . getAnnotation ( DecimalMin .class );
219
+ DecimalMin decimalMinAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMin .class );
194
220
if (decimalMinAnnotation != null && decimalMinAnnotation .inclusive ()) {
195
221
return new BigDecimal (decimalMinAnnotation .value ());
196
222
}
197
- PositiveOrZero positiveAnnotation = fieldOrMethod . getAnnotation ( PositiveOrZero .class );
223
+ PositiveOrZero positiveAnnotation = this . getAnnotationFromFieldOrGetter ( member , PositiveOrZero .class );
198
224
if (positiveAnnotation != null ) {
199
225
return BigDecimal .ZERO ;
200
226
}
@@ -204,18 +230,17 @@ private BigDecimal resolveNumberInclusiveMinimum(AccessibleObject fieldOrMethod,
204
230
/**
205
231
* Determine a number type's minimum (exclusive) value.
206
232
*
207
- * @param fieldOrMethod the field or method to check
208
- * @param type field's or method return value's type
233
+ * @param member the field or method to check
209
234
* @return specified exclusive minimum value (or null)
210
235
* @see DecimalMin
211
236
* @see Positive
212
237
*/
213
- private BigDecimal resolveNumberExclusiveMinimum (AccessibleObject fieldOrMethod , JavaType type ) {
214
- DecimalMin decimalMinAnnotation = fieldOrMethod . getAnnotation ( DecimalMin .class );
238
+ protected BigDecimal resolveNumberExclusiveMinimum (MemberScope <?, ?> member ) {
239
+ DecimalMin decimalMinAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMin .class );
215
240
if (decimalMinAnnotation != null && !decimalMinAnnotation .inclusive ()) {
216
241
return new BigDecimal (decimalMinAnnotation .value ());
217
242
}
218
- Positive positiveAnnotation = fieldOrMethod . getAnnotation ( Positive .class );
243
+ Positive positiveAnnotation = this . getAnnotationFromFieldOrGetter ( member , Positive .class );
219
244
if (positiveAnnotation != null ) {
220
245
return BigDecimal .ZERO ;
221
246
}
@@ -225,23 +250,22 @@ private BigDecimal resolveNumberExclusiveMinimum(AccessibleObject fieldOrMethod,
225
250
/**
226
251
* Determine a number type's maximum (inclusive) value.
227
252
*
228
- * @param fieldOrMethod the field or method to check
229
- * @param type field's or method return value's type
253
+ * @param member the field or method to check
230
254
* @return specified inclusive maximum value (or null)
231
255
* @see Max
232
256
* @see DecimalMax#inclusive()
233
257
* @see NegativeOrZero
234
258
*/
235
- private BigDecimal resolveNumberInclusiveMaximum (AccessibleObject fieldOrMethod , JavaType type ) {
236
- Max maxAnnotation = fieldOrMethod . getAnnotation ( Max .class );
259
+ protected BigDecimal resolveNumberInclusiveMaximum (MemberScope <?, ?> member ) {
260
+ Max maxAnnotation = this . getAnnotationFromFieldOrGetter ( member , Max .class );
237
261
if (maxAnnotation != null ) {
238
262
return new BigDecimal (maxAnnotation .value ());
239
263
}
240
- DecimalMax decimalMaxAnnotation = fieldOrMethod . getAnnotation ( DecimalMax .class );
264
+ DecimalMax decimalMaxAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMax .class );
241
265
if (decimalMaxAnnotation != null && decimalMaxAnnotation .inclusive ()) {
242
266
return new BigDecimal (decimalMaxAnnotation .value ());
243
267
}
244
- NegativeOrZero negativeAnnotation = fieldOrMethod . getAnnotation ( NegativeOrZero .class );
268
+ NegativeOrZero negativeAnnotation = this . getAnnotationFromFieldOrGetter ( member , NegativeOrZero .class );
245
269
if (negativeAnnotation != null ) {
246
270
return BigDecimal .ZERO ;
247
271
}
@@ -251,18 +275,17 @@ private BigDecimal resolveNumberInclusiveMaximum(AccessibleObject fieldOrMethod,
251
275
/**
252
276
* Determine a number type's maximum (exclusive) value.
253
277
*
254
- * @param fieldOrMethod the field or method to check
255
- * @param type field's or method return value's type
278
+ * @param member the field or method to check
256
279
* @return specified exclusive maximum value (or null)
257
280
* @see DecimalMax#inclusive()
258
281
* @see Negative
259
282
*/
260
- private BigDecimal resolveNumberExclusiveMaximum (AccessibleObject fieldOrMethod , JavaType type ) {
261
- DecimalMax decimalMaxAnnotation = fieldOrMethod . getAnnotation ( DecimalMax .class );
283
+ protected BigDecimal resolveNumberExclusiveMaximum (MemberScope <?, ?> member ) {
284
+ DecimalMax decimalMaxAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMax .class );
262
285
if (decimalMaxAnnotation != null && !decimalMaxAnnotation .inclusive ()) {
263
286
return new BigDecimal (decimalMaxAnnotation .value ());
264
287
}
265
- Negative negativeAnnotation = fieldOrMethod . getAnnotation ( Negative .class );
288
+ Negative negativeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Negative .class );
266
289
if (negativeAnnotation != null ) {
267
290
return BigDecimal .ZERO ;
268
291
}
0 commit comments