Skip to content

Commit 2835d9d

Browse files
micrycfrantuma
andcommitted
Spring - update nullable and required validation
Co-authored-by: frantuma <[email protected]>
1 parent 7879271 commit 2835d9d

20 files changed

+468
-25
lines changed

src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,7 @@ protected void setSchemaProperties(String name, CodegenProperty codegenProperty,
16151615
codegenProperty.defaultValue = toDefaultValue(schema);
16161616
codegenProperty.defaultValueWithParam = toDefaultValueWithParam(name, schema);
16171617
codegenProperty.jsonSchema = Json.pretty(schema);
1618+
codegenProperty.schemaType = schema.getType();
16181619
codegenProperty.nullable = Boolean.TRUE.equals(schema.getNullable());
16191620
codegenProperty.getVendorExtensions().put(CodegenConstants.IS_NULLABLE_EXT_NAME, Boolean.TRUE.equals(schema.getNullable()));
16201621
codegenProperty.getVendorExtensions().put(IS_NULLABLE_FALSE, Boolean.FALSE.equals(schema.getNullable()));

src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;
1717
import io.swagger.codegen.v3.generators.features.NotNullAnnotationFeatures;
1818
import io.swagger.codegen.v3.generators.handlebars.java.JavaHelper;
19+
import io.swagger.codegen.v3.utils.URLPathUtil;
1920
import io.swagger.v3.oas.models.OpenAPI;
2021
import io.swagger.v3.oas.models.Operation;
2122
import io.swagger.v3.oas.models.PathItem;
@@ -32,6 +33,7 @@
3233
import io.swagger.v3.oas.models.responses.ApiResponse;
3334
import io.swagger.v3.parser.util.SchemaTypeUtil;
3435
import java.io.File;
36+
import java.net.URL;
3537
import java.util.Arrays;
3638
import java.util.HashMap;
3739
import java.util.HashSet;
@@ -209,6 +211,7 @@ public AbstractJavaCodegen() {
209211
cliOptions.add(jeeSpec);
210212

211213
cliOptions.add(CliOption.newBoolean(USE_NULLABLE_FOR_NOTNULL, "Add @NotNull depending on `nullable` property instead of `required`"));
214+
212215
}
213216

214217
@Override
@@ -515,6 +518,7 @@ public void processOpts() {
515518
setJakarta(Boolean.parseBoolean(String.valueOf(additionalProperties.get(JAKARTA))));
516519
additionalProperties.put(JAKARTA, jakarta);
517520
}
521+
518522
}
519523

520524
private void sanitizeConfig() {
@@ -1155,6 +1159,11 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
11551159
operation.addExtension("x-accepts", accepts);
11561160
}
11571161
}
1162+
final URL urlInfo = URLPathUtil.getServerURL(openAPI);
1163+
if (urlInfo != null && StringUtils.isNotBlank(urlInfo.getPath())) {
1164+
additionalProperties.put("contextPathWithoutHost", urlInfo.getPath());
1165+
}
1166+
11581167
}
11591168

11601169
private static String getAccept(Operation operation) {

src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,14 @@ public class SpringCodegen extends AbstractJavaCodegen implements BeanValidation
6868
public static final String SPRING_BOOT_VERSION_2 = "springBootV2";
6969
public static final String DATE_PATTERN = "datePattern";
7070
public static final String DATE_TIME_PATTERN = "dateTimePattern";
71-
7271
public static final String THROWS_EXCEPTION = "throwsException";
7372

73+
public static final String VALIDATION_MODE_OPTION = "validationMode";
74+
public static final String VALIDATION_MODE_LEGACY = "legacy";
75+
public static final String VALIDATION_MODE_LEGACY_NULLABLE = "legacyNullable";
76+
public static final String VALIDATION_MODE_STRICT = "strict";
77+
public static final String VALIDATION_MODE_LOOSE = "loose";
78+
7479
protected String title = "swagger-petstore";
7580
protected String configPackage = "io.swagger.configuration";
7681
protected String basePackage = "io.swagger";
@@ -92,6 +97,7 @@ public class SpringCodegen extends AbstractJavaCodegen implements BeanValidation
9297
protected String springBootVersion = "2.1.16.RELEASE";
9398
protected boolean throwsException = false;
9499
private boolean notNullJacksonAnnotation = false;
100+
protected String validationMode = "strict";
95101

96102
public SpringCodegen() {
97103
super();
@@ -146,6 +152,15 @@ public SpringCodegen() {
146152
springBootVersionOption.setEnum(springBootEnum);
147153
cliOptions.add(springBootVersionOption);
148154

155+
CliOption validationMode = new CliOption(VALIDATION_MODE_OPTION, "Validation mode to apply");
156+
validationMode.setDefault(VALIDATION_MODE_STRICT);
157+
Map<String, String> validationModeOptions = new HashMap<String, String>();
158+
validationModeOptions.put(VALIDATION_MODE_STRICT, "Use Helper JsonNullable/NotUndefined on required+nullable fields, @NotNull on required, jackson validation on default");
159+
validationModeOptions.put(VALIDATION_MODE_LOOSE, "Use Helper JsonNullable/NotUndefined on required+nullable fields, @NotNull on required, no validation on default");
160+
validationModeOptions.put(VALIDATION_MODE_LEGACY, "Apply @NotNull on required fields");
161+
validationModeOptions.put(VALIDATION_MODE_LEGACY_NULLABLE, "Apply @NotNull when nullable is not defined or false, if useNullableForNotNull=false Apply @NotNull on required fields");
162+
validationMode.setEnum(validationModeOptions);
163+
cliOptions.add(validationMode);
149164
}
150165

151166
@Override
@@ -232,6 +247,11 @@ public void processOpts() {
232247
this.setTitle((String) additionalProperties.get(TITLE));
233248
}
234249

250+
if (additionalProperties.containsKey(VALIDATION_MODE_OPTION)) {
251+
this.setValidationMode((String) additionalProperties.get(VALIDATION_MODE_OPTION));
252+
}
253+
additionalProperties.put("is" + validationMode.substring(0, 1).toUpperCase() + validationMode.substring(1) + "Validation", true);
254+
235255
if (additionalProperties.containsKey(CONFIG_PACKAGE)) {
236256
this.setConfigPackage((String) additionalProperties.get(CONFIG_PACKAGE));
237257
}
@@ -297,6 +317,12 @@ public void processOpts() {
297317

298318
if (useBeanValidation) {
299319
writePropertyBack(USE_BEANVALIDATION, useBeanValidation);
320+
if (VALIDATION_MODE_LOOSE.equals(validationMode) || VALIDATION_MODE_STRICT.equals(validationMode)) {
321+
supportingFiles.add(new SupportingFile("NotUndefined.mustache",
322+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "NotUndefined.java"));
323+
supportingFiles.add(new SupportingFile("NotUndefinedValidator.mustache",
324+
(sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "NotUndefinedValidator.java"));
325+
}
300326
}
301327

302328
if (additionalProperties.containsKey(IMPLICIT_HEADERS)) {
@@ -845,6 +871,10 @@ public void setConfigPackage(String configPackage) {
845871
this.configPackage = configPackage;
846872
}
847873

874+
public void setValidationMode(String validationMode) {
875+
this.validationMode = validationMode;
876+
}
877+
848878
public void setBasePackage(String configPackage) {
849879
this.basePackage = configPackage;
850880
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package {{configPackage}};
2+
3+
import javax.validation.Constraint;
4+
import javax.validation.Payload;
5+
import java.lang.annotation.*;
6+
7+
@Target({ElementType.TYPE})
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Documented
10+
@Constraint(validatedBy = NotUndefinedValidator.class)
11+
public @interface NotUndefined {
12+
String message() default "field cannot be undefined";
13+
Class<?>[] groups() default {};
14+
Class<? extends Payload>[] payload() default {};
15+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package {{configPackage}};
2+
3+
import org.openapitools.jackson.nullable.JsonNullable;
4+
import javax.validation.ConstraintValidator;
5+
import javax.validation.ConstraintValidatorContext;
6+
import java.lang.reflect.Field;
7+
8+
public class NotUndefinedValidator implements ConstraintValidator<NotUndefined, Object>{
9+
10+
@Override
11+
public void initialize(NotUndefined constraintAnnotation) {
12+
}
13+
14+
@Override
15+
public boolean isValid(Object addressInformation, ConstraintValidatorContext context) {
16+
Class<?> objClass = addressInformation.getClass();
17+
Field[] fields = objClass.getDeclaredFields();
18+
for (Field field : fields) {
19+
if (field.getType().equals(JsonNullable.class)){
20+
field.setAccessible(true);
21+
try {
22+
Object value = field.get(addressInformation);
23+
if(value.equals(JsonNullable.undefined())){
24+
context.disableDefaultConstraintViolation();
25+
context.buildConstraintViolationWithTemplate(field.getName() + " cannot be undefined")
26+
.addConstraintViolation();
27+
return false;
28+
}
29+
} catch (IllegalAccessException e) {
30+
e.printStackTrace();
31+
}
32+
}
33+
}
34+
return true;
35+
}
36+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{{#useOas2}}
22
springfox.documentation.swagger.v2.path=/api-docs
3-
server.contextPath={{^contextPath}}/{{/contextPath}}{{#contextPath}}{{contextPath}}{{/contextPath}}
3+
server.contextPath={{^contextPathWithoutHost}}/{{/contextPathWithoutHost}}{{#contextPathWithoutHost}}{{contextPathWithoutHost}}{{/contextPathWithoutHost}}
44
{{/useOas2}}
55
{{^useOas2}}
66
springdoc.api-docs.path=/api-docs
77
{{/useOas2}}
8-
server.servlet.contextPath={{^contextPath}}/{{/contextPath}}{{#contextPath}}{{contextPath}}{{/contextPath}}
8+
server.servlet.contextPath={{^contextPathWithoutHost}}/{{/contextPathWithoutHost}}{{#contextPathWithoutHost}}{{contextPathWithoutHost}}{{/contextPathWithoutHost}}
99
server.port={{serverPort}}
1010
spring.jackson.date-format={{basePackage}}.RFC3339DateFormat
1111
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{#isLegacyValidation}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/isLegacyValidation}}{{#isLegacyNullableValidation}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/isLegacyNullableValidation}}
2+
{{#isStrictValidation}}{{#required}}{{#nullable}} public {{classname}} {{name}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}{{^nullable}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}{{^required}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isStrictValidation}}
3+
{{#isLooseValidation}}{{#required}}{{#nullable}} public {{classname}} {{name}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}{{^nullable}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}{{^required}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isLooseValidation}}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{{#isLegacyValidation}}{{#required}}@NotNull{{/required}} {{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
2+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
3+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
4+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/isLegacyValidation}}{{#isLegacyNullableValidation}}{{#required}}{{^useNullableForNotNull}}@NotNull{{/useNullableForNotNull}}{{/required}}
5+
{{#useNullableForNotNull}}{{^nullable}}@NotNull{{/nullable}}{{/useNullableForNotNull}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
6+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
7+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
8+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/isLegacyNullableValidation}}{{#isStrictValidation}}{{#required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
9+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
10+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
11+
{{>beanValidationCore}} public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
12+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
13+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
14+
@NotNull
15+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{^required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
16+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
17+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
18+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() {
19+
{{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
20+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
21+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
22+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{/isStrictValidation}} {{#isLooseValidation}}{{#required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
23+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
24+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
25+
{{>beanValidationCore}} public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
26+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
27+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
28+
@NotNull
29+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{^required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
30+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
31+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
32+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
33+
@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
34+
@Valid{{/isPrimitiveType}}{{/isNotContainer}}
35+
{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{/isLooseValidation}}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{#isLegacyValidation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}){ {{/isLegacyValidation}}
2+
{{#isLegacyNullableValidation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}){ {{/isLegacyNullableValidation}}
3+
{{#isStrictValidation}}
4+
{{#required}}{{#nullable}} public void {{setter}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}
5+
{{^nullable}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}
6+
{{^required}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isStrictValidation}}{{#isLooseValidation}}
7+
{{#required}}{{#nullable}} public void {{setter}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}
8+
{{^nullable}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}
9+
{{^required}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isLooseValidation}}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{{#isLegacyValidation}} private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};{{/isLegacyValidation}}{{#isLegacyNullableValidation}} private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};{{/isLegacyNullableValidation}}
2+
{{#isStrictValidation}}
3+
{{#required}}
4+
{{#nullable}}
5+
private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.undefined();
6+
{{/nullable}}
7+
{{^nullable}}
8+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
9+
{{/nullable}}
10+
{{/required}}
11+
{{^required}}
12+
{{#nullable}}
13+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
14+
{{/nullable}}
15+
{{^nullable}}
16+
@JsonInclude(JsonInclude.Include.NON_ABSENT) // Exclude from JSON if absent
17+
@JsonSetter(nulls = Nulls.FAIL) // FAIL setting if the value is null
18+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
19+
{{/nullable}}
20+
{{/required}}
21+
{{/isStrictValidation}}
22+
{{#isLooseValidation}}
23+
{{#required}}
24+
{{#nullable}}
25+
private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.undefined();
26+
{{/nullable}}
27+
{{^nullable}}
28+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
29+
{{/nullable}}
30+
{{/required}}
31+
{{^required}}
32+
{{#nullable}}
33+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
34+
{{/nullable}}
35+
{{^nullable}}
36+
private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
37+
{{/nullable}}
38+
{{/required}}
39+
{{/isLooseValidation}}

0 commit comments

Comments
 (0)