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

Commit 404464a

Browse files
adjust to generator version 3
and extend annotation support to field/getter paris
1 parent c532897 commit 404464a

File tree

6 files changed

+420
-110
lines changed

6 files changed

+420
-110
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- Consider the same validation annotations on a getter method also for its field
10+
- Consider the same validation annotations on a field also for its getter method
11+
12+
### Changed
13+
- Internal changes to comply with latest `jsonschema-generator` release `v3.x`
814

915
## v1.0.0 – 2019-05-22
1016
### Added

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
# Java JSON Schema Generator – Module javax.validation
22
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.victools/jsonschema-module-javax-validation/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.victools/jsonschema-module-javax-validation)
33

4-
Module for the `jsonschema-generator` – deriving JSON Schema attributes from `javax.validation` annotations.
4+
Module for the `jsonschema-generator` – deriving JSON Schema attributes from `javax.validation.constraints` annotations.
5+
6+
## Features
7+
1. Determine whether a member is not nullable, base assumption being that all fields and method return values are nullable if not annotated. Based on `@NotNull`/`@Null`/`@NotEmpty`/`@NotBlank`
8+
2. Populate "minItems" and "maxItems" for containers (i.e. arrays and collections). Based on `@Size`/`@NotEmpty`
9+
3. Populate "minLength" and "maxLength" for strings. Based on `@Size`/`@NotEmpty`/`@NotBlank`
10+
4. Populate "minimum"/"exclusiveMinimum" for numbers. Based on `@Min`/`@DecimalMin`/`@Positive`/`@PositiveOrZero`
11+
5. Populate "maximum"/"exclusiveMaximum" for numbers. Based on `@Max`/`@DecimalMax`/`@Negative`/`@NegativeOrZero`
12+
13+
Schema attributes derived from validation annotations on fields are also applied to their respective getter methods.
14+
Schema attributes derived from validation annotations on getter methods are also applied to their associated fields.
515

616
## Usage
717
### Dependency (Maven)
8-
918
```xml
1019
<dependency>
1120
<groupId>com.github.victools</groupId>

pom.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<groupId>com.github.victools</groupId>
1010
<artifactId>jsonschema-module-javax-validation</artifactId>
11-
<version>1.1.0-SNAPSHOT</version>
11+
<version>3.0.0-SNAPSHOT</version>
1212
<packaging>jar</packaging>
1313

1414
<licenses>
@@ -58,6 +58,9 @@
5858
<maven.compiler.source>1.8</maven.compiler.source>
5959
<maven.compiler.target>1.8</maven.compiler.target>
6060

61+
<version.generator>3.0.0</version.generator>
62+
63+
<version.javax.validation>2.0.1.Final</version.javax.validation>
6164
<version.junit>4.12</version.junit>
6265
<version.junitparams>1.1.1</version.junitparams>
6366
<version.mockito>2.27.0</version.mockito>
@@ -67,13 +70,13 @@
6770
<dependency>
6871
<groupId>com.github.victools</groupId>
6972
<artifactId>jsonschema-generator</artifactId>
70-
<version>1.0.1</version>
73+
<version>${version.generator}</version>
7174
<scope>provided</scope>
7275
</dependency>
7376
<dependency>
7477
<groupId>javax.validation</groupId>
7578
<artifactId>validation-api</artifactId>
76-
<version>2.0.1.Final</version>
79+
<version>${version.javax.validation}</version>
7780
<scope>provided</scope>
7881
</dependency>
7982
<!-- junit is only required in "test" scope -->

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

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
package com.github.victools.jsonschema.module.javax.validation;
1818

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;
2022
import com.github.victools.jsonschema.generator.Module;
2123
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
2224
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;
2526
import java.math.BigDecimal;
2627
import javax.validation.constraints.DecimalMax;
2728
import javax.validation.constraints.DecimalMin;
@@ -38,7 +39,13 @@
3839
import javax.validation.constraints.Size;
3940

4041
/**
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>
4249
*/
4350
public class JavaxValidationModule implements Module {
4451

@@ -53,7 +60,7 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
5360
*
5461
* @param configPart config builder part to add configurations to
5562
*/
56-
private void applyToConfigPart(SchemaGeneratorConfigPart<? extends AccessibleObject> configPart) {
63+
private void applyToConfigPart(SchemaGeneratorConfigPart<?> configPart) {
5764
configPart.withNullableCheck(this::isNullable);
5865
configPart.withArrayMinItemsResolver(this::resolveArrayMinItems);
5966
configPart.withArrayMaxItemsResolver(this::resolveArrayMaxItems);
@@ -65,21 +72,47 @@ private void applyToConfigPart(SchemaGeneratorConfigPart<? extends AccessibleObj
6572
configPart.withNumberExclusiveMaximumResolver(this::resolveNumberExclusiveMaximum);
6673
}
6774

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+
68102
/**
69103
* Determine whether a given field or method is annotated to be not nullable.
70104
*
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)
74107
*/
75-
private Boolean isNullable(AccessibleObject fieldOrMethod, JavaType type) {
108+
protected Boolean isNullable(MemberScope<?, ?> member) {
76109
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) {
80113
// field is specifically NOT nullable
81114
result = Boolean.FALSE;
82-
} else if (fieldOrMethod.isAnnotationPresent(Null.class)) {
115+
} else if (this.getAnnotationFromFieldOrGetter(member, Null.class) != null) {
83116
// field is specifically null (and thereby nullable)
84117
result = Boolean.TRUE;
85118
} else {
@@ -91,19 +124,18 @@ private Boolean isNullable(AccessibleObject fieldOrMethod, JavaType type) {
91124
/**
92125
* Determine a given array type's minimum number of items.
93126
*
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
96128
* @return specified minimum number of array items (or null)
97129
* @see Size
98130
*/
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);
102134
if (sizeAnnotation != null && sizeAnnotation.min() > 0) {
103135
// minimum length greater than the default 0 was specified
104136
return sizeAnnotation.min();
105137
}
106-
if (fieldOrMethod.isAnnotationPresent(NotEmpty.class)) {
138+
if (this.getAnnotationFromFieldOrGetter(member, NotEmpty.class) != null) {
107139
return 1;
108140
}
109141
}
@@ -113,14 +145,13 @@ private Integer resolveArrayMinItems(AccessibleObject fieldOrMethod, JavaType ty
113145
/**
114146
* Determine a given array type's maximum number of items.
115147
*
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
118149
* @return specified maximum number of array items (or null)
119150
* @see Size
120151
*/
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);
124155
if (sizeAnnotation != null && sizeAnnotation.max() < 2147483647) {
125156
// maximum length below the default 2147483647 was specified
126157
return sizeAnnotation.max();
@@ -132,23 +163,21 @@ private Integer resolveArrayMaxItems(AccessibleObject fieldOrMethod, JavaType ty
132163
/**
133164
* Determine a given text type's minimum number of characters.
134165
*
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
137167
* @return specified minimum number of characters (or null)
138168
* @see Size
139169
* @see NotEmpty
140170
* @see NotBlank
141171
*/
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);
146175
if (sizeAnnotation != null && sizeAnnotation.min() > 0) {
147176
// minimum length greater than the default 0 was specified
148177
return sizeAnnotation.min();
149178
}
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) {
152181
return 1;
153182
}
154183
}
@@ -158,15 +187,13 @@ private Integer resolveStringMinLength(AccessibleObject fieldOrMethod, JavaType
158187
/**
159188
* Determine a given text type's maximum number of characters.
160189
*
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
163191
* @return specified minimum number of characters (or null)
164192
* @see Size
165193
*/
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);
170197
if (sizeAnnotation != null && sizeAnnotation.max() < 2147483647) {
171198
// maximum length below the default 2147483647 was specified
172199
return sizeAnnotation.max();
@@ -178,23 +205,22 @@ private Integer resolveStringMaxLength(AccessibleObject fieldOrMethod, JavaType
178205
/**
179206
* Determine a number type's minimum (inclusive) value.
180207
*
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
183209
* @return specified inclusive minimum value (or null)
184210
* @see Min
185211
* @see DecimalMin
186212
* @see PositiveOrZero
187213
*/
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);
190216
if (minAnnotation != null) {
191217
return new BigDecimal(minAnnotation.value());
192218
}
193-
DecimalMin decimalMinAnnotation = fieldOrMethod.getAnnotation(DecimalMin.class);
219+
DecimalMin decimalMinAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMin.class);
194220
if (decimalMinAnnotation != null && decimalMinAnnotation.inclusive()) {
195221
return new BigDecimal(decimalMinAnnotation.value());
196222
}
197-
PositiveOrZero positiveAnnotation = fieldOrMethod.getAnnotation(PositiveOrZero.class);
223+
PositiveOrZero positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, PositiveOrZero.class);
198224
if (positiveAnnotation != null) {
199225
return BigDecimal.ZERO;
200226
}
@@ -204,18 +230,17 @@ private BigDecimal resolveNumberInclusiveMinimum(AccessibleObject fieldOrMethod,
204230
/**
205231
* Determine a number type's minimum (exclusive) value.
206232
*
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
209234
* @return specified exclusive minimum value (or null)
210235
* @see DecimalMin
211236
* @see Positive
212237
*/
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);
215240
if (decimalMinAnnotation != null && !decimalMinAnnotation.inclusive()) {
216241
return new BigDecimal(decimalMinAnnotation.value());
217242
}
218-
Positive positiveAnnotation = fieldOrMethod.getAnnotation(Positive.class);
243+
Positive positiveAnnotation = this.getAnnotationFromFieldOrGetter(member, Positive.class);
219244
if (positiveAnnotation != null) {
220245
return BigDecimal.ZERO;
221246
}
@@ -225,23 +250,22 @@ private BigDecimal resolveNumberExclusiveMinimum(AccessibleObject fieldOrMethod,
225250
/**
226251
* Determine a number type's maximum (inclusive) value.
227252
*
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
230254
* @return specified inclusive maximum value (or null)
231255
* @see Max
232256
* @see DecimalMax#inclusive()
233257
* @see NegativeOrZero
234258
*/
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);
237261
if (maxAnnotation != null) {
238262
return new BigDecimal(maxAnnotation.value());
239263
}
240-
DecimalMax decimalMaxAnnotation = fieldOrMethod.getAnnotation(DecimalMax.class);
264+
DecimalMax decimalMaxAnnotation = this.getAnnotationFromFieldOrGetter(member, DecimalMax.class);
241265
if (decimalMaxAnnotation != null && decimalMaxAnnotation.inclusive()) {
242266
return new BigDecimal(decimalMaxAnnotation.value());
243267
}
244-
NegativeOrZero negativeAnnotation = fieldOrMethod.getAnnotation(NegativeOrZero.class);
268+
NegativeOrZero negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, NegativeOrZero.class);
245269
if (negativeAnnotation != null) {
246270
return BigDecimal.ZERO;
247271
}
@@ -251,18 +275,17 @@ private BigDecimal resolveNumberInclusiveMaximum(AccessibleObject fieldOrMethod,
251275
/**
252276
* Determine a number type's maximum (exclusive) value.
253277
*
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
256279
* @return specified exclusive maximum value (or null)
257280
* @see DecimalMax#inclusive()
258281
* @see Negative
259282
*/
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);
262285
if (decimalMaxAnnotation != null && !decimalMaxAnnotation.inclusive()) {
263286
return new BigDecimal(decimalMaxAnnotation.value());
264287
}
265-
Negative negativeAnnotation = fieldOrMethod.getAnnotation(Negative.class);
288+
Negative negativeAnnotation = this.getAnnotationFromFieldOrGetter(member, Negative.class);
266289
if (negativeAnnotation != null) {
267290
return BigDecimal.ZERO;
268291
}

0 commit comments

Comments
 (0)