diff --git a/pom.xml b/pom.xml
index 33f5f45881..31a7b091ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
io.swagger.codegen.v3
swagger-codegen-generators
- 1.0.51
+ 1.0.52-SNAPSHOT
jar
@@ -262,7 +262,7 @@
8
- 3.0.61
+ 3.0.62-SNAPSHOT
2.1.22
2.2.21
2.17.0
diff --git a/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java b/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java
index cd1009c5ba..f898c218d3 100644
--- a/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java
+++ b/src/main/java/io/swagger/codegen/v3/generators/DefaultCodegenConfig.java
@@ -1615,6 +1615,7 @@ protected void setSchemaProperties(String name, CodegenProperty codegenProperty,
codegenProperty.defaultValue = toDefaultValue(schema);
codegenProperty.defaultValueWithParam = toDefaultValueWithParam(name, schema);
codegenProperty.jsonSchema = Json.pretty(schema);
+ codegenProperty.schemaType = schema.getType();
codegenProperty.nullable = Boolean.TRUE.equals(schema.getNullable());
codegenProperty.getVendorExtensions().put(CodegenConstants.IS_NULLABLE_EXT_NAME, Boolean.TRUE.equals(schema.getNullable()));
codegenProperty.getVendorExtensions().put(IS_NULLABLE_FALSE, Boolean.FALSE.equals(schema.getNullable()));
diff --git a/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java
index ebc1388158..11dd304657 100644
--- a/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java
+++ b/src/main/java/io/swagger/codegen/v3/generators/java/AbstractJavaCodegen.java
@@ -16,6 +16,7 @@
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;
import io.swagger.codegen.v3.generators.features.NotNullAnnotationFeatures;
import io.swagger.codegen.v3.generators.handlebars.java.JavaHelper;
+import io.swagger.codegen.v3.utils.URLPathUtil;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
@@ -32,6 +33,7 @@
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import java.io.File;
+import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -209,6 +211,7 @@ public AbstractJavaCodegen() {
cliOptions.add(jeeSpec);
cliOptions.add(CliOption.newBoolean(USE_NULLABLE_FOR_NOTNULL, "Add @NotNull depending on `nullable` property instead of `required`"));
+
}
@Override
@@ -515,6 +518,7 @@ public void processOpts() {
setJakarta(Boolean.parseBoolean(String.valueOf(additionalProperties.get(JAKARTA))));
additionalProperties.put(JAKARTA, jakarta);
}
+
}
private void sanitizeConfig() {
@@ -1155,6 +1159,11 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
operation.addExtension("x-accepts", accepts);
}
}
+ final URL urlInfo = URLPathUtil.getServerURL(openAPI);
+ if (urlInfo != null && StringUtils.isNotBlank(urlInfo.getPath())) {
+ additionalProperties.put("contextPathWithoutHost", urlInfo.getPath());
+ }
+
}
private static String getAccept(Operation operation) {
diff --git a/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java
index 29b7276a4f..968e672084 100644
--- a/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java
+++ b/src/main/java/io/swagger/codegen/v3/generators/java/SpringCodegen.java
@@ -68,9 +68,14 @@ public class SpringCodegen extends AbstractJavaCodegen implements BeanValidation
public static final String SPRING_BOOT_VERSION_2 = "springBootV2";
public static final String DATE_PATTERN = "datePattern";
public static final String DATE_TIME_PATTERN = "dateTimePattern";
-
public static final String THROWS_EXCEPTION = "throwsException";
+ public static final String VALIDATION_MODE_OPTION = "validationMode";
+ public static final String VALIDATION_MODE_LEGACY = "legacy";
+ public static final String VALIDATION_MODE_LEGACY_NULLABLE = "legacyNullable";
+ public static final String VALIDATION_MODE_STRICT = "strict";
+ public static final String VALIDATION_MODE_LOOSE = "loose";
+
protected String title = "swagger-petstore";
protected String configPackage = "io.swagger.configuration";
protected String basePackage = "io.swagger";
@@ -92,6 +97,7 @@ public class SpringCodegen extends AbstractJavaCodegen implements BeanValidation
protected String springBootVersion = "2.1.16.RELEASE";
protected boolean throwsException = false;
private boolean notNullJacksonAnnotation = false;
+ protected String validationMode = "strict";
public SpringCodegen() {
super();
@@ -146,6 +152,15 @@ public SpringCodegen() {
springBootVersionOption.setEnum(springBootEnum);
cliOptions.add(springBootVersionOption);
+ CliOption validationMode = new CliOption(VALIDATION_MODE_OPTION, "Validation mode to apply");
+ validationMode.setDefault(VALIDATION_MODE_STRICT);
+ Map validationModeOptions = new HashMap();
+ validationModeOptions.put(VALIDATION_MODE_STRICT, "Use Helper JsonNullable/NotUndefined on required+nullable fields, @NotNull on required, jackson validation on default");
+ validationModeOptions.put(VALIDATION_MODE_LOOSE, "Use Helper JsonNullable/NotUndefined on required+nullable fields, @NotNull on required, no validation on default");
+ validationModeOptions.put(VALIDATION_MODE_LEGACY, "Apply @NotNull on required fields");
+ validationModeOptions.put(VALIDATION_MODE_LEGACY_NULLABLE, "Apply @NotNull when nullable is not defined or false, if useNullableForNotNull=false Apply @NotNull on required fields");
+ validationMode.setEnum(validationModeOptions);
+ cliOptions.add(validationMode);
}
@Override
@@ -232,6 +247,11 @@ public void processOpts() {
this.setTitle((String) additionalProperties.get(TITLE));
}
+ if (additionalProperties.containsKey(VALIDATION_MODE_OPTION)) {
+ this.setValidationMode((String) additionalProperties.get(VALIDATION_MODE_OPTION));
+ }
+ additionalProperties.put("is" + validationMode.substring(0, 1).toUpperCase() + validationMode.substring(1) + "Validation", true);
+
if (additionalProperties.containsKey(CONFIG_PACKAGE)) {
this.setConfigPackage((String) additionalProperties.get(CONFIG_PACKAGE));
}
@@ -297,6 +317,12 @@ public void processOpts() {
if (useBeanValidation) {
writePropertyBack(USE_BEANVALIDATION, useBeanValidation);
+ if (VALIDATION_MODE_LOOSE.equals(validationMode) || VALIDATION_MODE_STRICT.equals(validationMode)) {
+ supportingFiles.add(new SupportingFile("NotUndefined.mustache",
+ (sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "NotUndefined.java"));
+ supportingFiles.add(new SupportingFile("NotUndefinedValidator.mustache",
+ (sourceFolder + File.separator + configPackage).replace(".", java.io.File.separator), "NotUndefinedValidator.java"));
+ }
}
if (additionalProperties.containsKey(IMPLICIT_HEADERS)) {
@@ -845,6 +871,10 @@ public void setConfigPackage(String configPackage) {
this.configPackage = configPackage;
}
+ public void setValidationMode(String validationMode) {
+ this.validationMode = validationMode;
+ }
+
public void setBasePackage(String configPackage) {
this.basePackage = configPackage;
}
diff --git a/src/main/resources/handlebars/JavaSpring/NotUndefined.mustache b/src/main/resources/handlebars/JavaSpring/NotUndefined.mustache
new file mode 100644
index 0000000000..be3adc1b42
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/NotUndefined.mustache
@@ -0,0 +1,15 @@
+package {{configPackage}};
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = NotUndefinedValidator.class)
+public @interface NotUndefined {
+ String message() default "field cannot be undefined";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/src/main/resources/handlebars/JavaSpring/NotUndefinedValidator.mustache b/src/main/resources/handlebars/JavaSpring/NotUndefinedValidator.mustache
new file mode 100644
index 0000000000..92dc13018e
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/NotUndefinedValidator.mustache
@@ -0,0 +1,36 @@
+package {{configPackage}};
+
+import org.openapitools.jackson.nullable.JsonNullable;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.lang.reflect.Field;
+
+public class NotUndefinedValidator implements ConstraintValidator{
+
+ @Override
+ public void initialize(NotUndefined constraintAnnotation) {
+ }
+
+ @Override
+ public boolean isValid(Object addressInformation, ConstraintValidatorContext context) {
+ Class> objClass = addressInformation.getClass();
+ Field[] fields = objClass.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getType().equals(JsonNullable.class)){
+ field.setAccessible(true);
+ try {
+ Object value = field.get(addressInformation);
+ if(value.equals(JsonNullable.undefined())){
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(field.getName() + " cannot be undefined")
+ .addConstraintViolation();
+ return false;
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/application.mustache b/src/main/resources/handlebars/JavaSpring/application.mustache
index 5ef27edcf1..39ea0037d4 100644
--- a/src/main/resources/handlebars/JavaSpring/application.mustache
+++ b/src/main/resources/handlebars/JavaSpring/application.mustache
@@ -1,11 +1,11 @@
{{#useOas2}}
springfox.documentation.swagger.v2.path=/api-docs
-server.contextPath={{^contextPath}}/{{/contextPath}}{{#contextPath}}{{contextPath}}{{/contextPath}}
+server.contextPath={{^contextPathWithoutHost}}/{{/contextPathWithoutHost}}{{#contextPathWithoutHost}}{{contextPathWithoutHost}}{{/contextPathWithoutHost}}
{{/useOas2}}
{{^useOas2}}
springdoc.api-docs.path=/api-docs
{{/useOas2}}
-server.servlet.contextPath={{^contextPath}}/{{/contextPath}}{{#contextPath}}{{contextPath}}{{/contextPath}}
+server.servlet.contextPath={{^contextPathWithoutHost}}/{{/contextPathWithoutHost}}{{#contextPathWithoutHost}}{{contextPathWithoutHost}}{{/contextPathWithoutHost}}
server.port={{serverPort}}
spring.jackson.date-format={{basePackage}}.RFC3339DateFormat
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/beanValidationFluentSetter.mustache b/src/main/resources/handlebars/JavaSpring/beanValidationFluentSetter.mustache
new file mode 100644
index 0000000000..c3ef986297
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/beanValidationFluentSetter.mustache
@@ -0,0 +1,3 @@
+{{#isLegacyValidation}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/isLegacyValidation}}{{#isLegacyNullableValidation}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/isLegacyNullableValidation}}
+{{#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}}
+{{#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}}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/beanValidationGetter.mustache b/src/main/resources/handlebars/JavaSpring/beanValidationGetter.mustache
new file mode 100644
index 0000000000..ff6ca48620
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/beanValidationGetter.mustache
@@ -0,0 +1,35 @@
+{{#isLegacyValidation}}{{#required}}@NotNull{{/required}} {{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/isLegacyValidation}}{{#isLegacyNullableValidation}}{{#required}}{{^useNullableForNotNull}}@NotNull{{/useNullableForNotNull}}{{/required}}
+{{#useNullableForNotNull}}{{^nullable}}@NotNull{{/nullable}}{{/useNullableForNotNull}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/isLegacyNullableValidation}}{{#isStrictValidation}}{{#required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+ @NotNull
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{^required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() {
+{{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{/isStrictValidation}} {{#isLooseValidation}}{{#required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+ @NotNull
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{^required}}{{#nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{^nullable}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
+@Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{#isNotContainer}}{{^isPrimitiveType}}
+@Valid{{/isPrimitiveType}}{{/isNotContainer}}
+{{>beanValidationCore}} public {{{datatypeWithEnum}}} {{getter}}() { {{/nullable}}{{/required}}{{/isLooseValidation}}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/beanValidationSetter.mustache b/src/main/resources/handlebars/JavaSpring/beanValidationSetter.mustache
new file mode 100644
index 0000000000..e38de32e7c
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/beanValidationSetter.mustache
@@ -0,0 +1,9 @@
+{{#isLegacyValidation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}){ {{/isLegacyValidation}}
+{{#isLegacyNullableValidation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}){ {{/isLegacyNullableValidation}}
+{{#isStrictValidation}}
+{{#required}}{{#nullable}} public void {{setter}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}
+{{^nullable}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}
+{{^required}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isStrictValidation}}{{#isLooseValidation}}
+{{#required}}{{#nullable}} public void {{setter}}(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { {{/nullable}}
+{{^nullable}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/nullable}}{{/required}}
+{{^required}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/required}}{{/isLooseValidation}}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/beanValidationVariable.mustache b/src/main/resources/handlebars/JavaSpring/beanValidationVariable.mustache
new file mode 100644
index 0000000000..ec3d29579a
--- /dev/null
+++ b/src/main/resources/handlebars/JavaSpring/beanValidationVariable.mustache
@@ -0,0 +1,39 @@
+{{#isLegacyValidation}} private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};{{/isLegacyValidation}}{{#isLegacyNullableValidation}} private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};{{/isLegacyNullableValidation}}
+{{#isStrictValidation}}
+{{#required}}
+{{#nullable}}
+ private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.undefined();
+{{/nullable}}
+{{^nullable}}
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{/required}}
+{{^required}}
+{{#nullable}}
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{^nullable}}
+ @JsonInclude(JsonInclude.Include.NON_ABSENT) // Exclude from JSON if absent
+ @JsonSetter(nulls = Nulls.FAIL) // FAIL setting if the value is null
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{/required}}
+{{/isStrictValidation}}
+{{#isLooseValidation}}
+{{#required}}
+{{#nullable}}
+ private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.undefined();
+{{/nullable}}
+{{^nullable}}
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{/required}}
+{{^required}}
+{{#nullable}}
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{^nullable}}
+ private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{/nullable}}
+{{/required}}
+{{/isLooseValidation}}
\ No newline at end of file
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/pom.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/pom.mustache
index 54fbe99edd..1a18ebd250 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/pom.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/pom.mustache
@@ -120,9 +120,21 @@
2.6.4
{{/threetenbp}}
- {{#useBeanValidation}}
-
+ {{#useBeanValidation}}{{#isStrictValidation}}
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+ {{/isStrictValidation}}{{#isLooseValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+ {{/isLooseValidation}}
{{#jakarta}}
jakarta.validation
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/swagger2SpringBoot.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/swagger2SpringBoot.mustache
index 42167055ab..46fd67794d 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/swagger2SpringBoot.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot/swagger2SpringBoot.mustache
@@ -8,6 +8,18 @@ import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
+{{#useBeanValidation}}
+{{#isStrictValidation}}
+import org.openapitools.jackson.nullable.JsonNullableModule;
+import org.springframework.context.annotation.Bean;
+import com.fasterxml.jackson.databind.Module;
+{{/isStrictValidation}}
+{{#isLooseValidation}}
+ import org.openapitools.jackson.nullable.JsonNullableModule;
+ import org.springframework.context.annotation.Bean;
+ import com.fasterxml.jackson.databind.Module;
+{{/isLooseValidation}}
+{{/useBeanValidation}}
{{#useOas2}}
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@@ -34,6 +46,20 @@ public class Swagger2SpringBoot implements CommandLineRunner {
public static void main(String[] args) throws Exception {
new SpringApplication(Swagger2SpringBoot.class).run(args);
}
+ {{#useBeanValidation}}
+ {{#isStrictValidation}}
+ @Bean
+ public Module jsonNullableModule() {
+ return new JsonNullableModule();
+ }
+ {{/isStrictValidation}}
+ {{#isLooseValidation}}
+ @Bean
+ public Module jsonNullableModule() {
+ return new JsonNullableModule();
+ }
+ {{/isLooseValidation}}
+ {{/useBeanValidation}}
@Configuration
static class CustomDateConfig extends WebMvcConfigurerAdapter {
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/openAPISpringBoot.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/openAPISpringBoot.mustache
index 876248c766..03ce8621d8 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/openAPISpringBoot.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/openAPISpringBoot.mustache
@@ -11,6 +11,19 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+{{#useBeanValidation}}
+{{#isStrictValidation}}
+import org.openapitools.jackson.nullable.JsonNullableModule;
+import org.springframework.context.annotation.Bean;
+import com.fasterxml.jackson.databind.Module;
+{{/isStrictValidation}}
+{{#isLooseValidation}}
+import org.openapitools.jackson.nullable.JsonNullableModule;
+import org.springframework.context.annotation.Bean;
+import com.fasterxml.jackson.databind.Module;
+{{/isLooseValidation}}
+{{/useBeanValidation}}
+
@SpringBootApplication
@ComponentScan(basePackages = { "{{basePackage}}", "{{apiPackage}}" , "{{configPackage}}"})
@@ -26,6 +39,20 @@ public class OpenAPISpringBoot implements CommandLineRunner {
public static void main(String[] args) throws Exception {
new SpringApplication(OpenAPISpringBoot.class).run(args);
}
+{{#useBeanValidation}}
+ {{#isStrictValidation}}
+ @Bean
+ public Module jsonNullableModule() {
+ return new JsonNullableModule();
+ }
+ {{/isStrictValidation}}
+ {{#isLooseValidation}}
+ @Bean
+ public Module jsonNullableModule() {
+ return new JsonNullableModule();
+ }
+ {{/isLooseValidation}}
+{{/useBeanValidation}}
@Configuration
static class CustomDateConfig extends WebMvcConfigurationSupport {
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/pom.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/pom.mustache
index ccd6595351..d1713dd3e7 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/pom.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-boot3/pom.mustache
@@ -50,8 +50,21 @@
jackson-dataformat-xml
{{/withXml}}
- {{#useBeanValidation}}
+{{#useBeanValidation}}{{#isStrictValidation}}
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isStrictValidation}}{{#isLooseValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isLooseValidation}}
jakarta.validation
jakarta.validation-api
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-cloud/pom.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-cloud/pom.mustache
index 6a68c8141c..7a7ca20511 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-cloud/pom.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-cloud/pom.mustache
@@ -111,9 +111,21 @@
2.6.4
{{/threetenbp}}
-{{#useBeanValidation}}
-
- {{#jakarta}}
+{{#useBeanValidation}}{{#isStrictValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isStrictValidation}}{{#isLooseValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isLooseValidation}}{{#jakarta}}
jakarta.validation
jakarta.validation-api
diff --git a/src/main/resources/handlebars/JavaSpring/libraries/spring-mvc/pom.mustache b/src/main/resources/handlebars/JavaSpring/libraries/spring-mvc/pom.mustache
index 764393a6bd..5cb1579265 100644
--- a/src/main/resources/handlebars/JavaSpring/libraries/spring-mvc/pom.mustache
+++ b/src/main/resources/handlebars/JavaSpring/libraries/spring-mvc/pom.mustache
@@ -189,9 +189,21 @@
${servlet-api-version}
{{/jakarta}}
-{{#useBeanValidation}}
-
- {{#jakarta}}
+{{#useBeanValidation}}{{#isStrictValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isStrictValidation}}{{#isLooseValidation}}
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+{{/isLooseValidation}}{{#jakarta}}
jakarta.validation
jakarta.validation-api
diff --git a/src/main/resources/handlebars/JavaSpring/model.mustache b/src/main/resources/handlebars/JavaSpring/model.mustache
index 681b07890c..16c0b0228d 100644
--- a/src/main/resources/handlebars/JavaSpring/model.mustache
+++ b/src/main/resources/handlebars/JavaSpring/model.mustache
@@ -9,6 +9,17 @@ import java.io.Serializable;
{{/serializableModel}}
{{#useBeanValidation}}
import org.springframework.validation.annotation.Validated;
+{{#isLooseValidation}}
+import org.openapitools.jackson.nullable.JsonNullable;
+import io.swagger.configuration.NotUndefined;
+{{/isLooseValidation}}
+{{#isStrictValidation}}
+import org.openapitools.jackson.nullable.JsonNullable;
+import io.swagger.configuration.NotUndefined;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+{{/isStrictValidation}}
{{#jakarta}}
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
diff --git a/src/main/resources/handlebars/JavaSpring/pojo.mustache b/src/main/resources/handlebars/JavaSpring/pojo.mustache
index 8c87f3bb8f..f00a0b7406 100644
--- a/src/main/resources/handlebars/JavaSpring/pojo.mustache
+++ b/src/main/resources/handlebars/JavaSpring/pojo.mustache
@@ -3,6 +3,7 @@
*/{{#description}}
{{#useOas2}}@ApiModel{{/useOas2}}{{^useOas2}}@Schema{{/useOas2}}(description = "{{{description}}}"){{/description}}
{{#useBeanValidation}}@Validated{{/useBeanValidation}}
+{{#useBeanValidation}}{{#isStrictValidation}}@NotUndefined{{/isStrictValidation}}{{#isLooseValidation}}@NotUndefined{{/isLooseValidation}}{{/useBeanValidation}}
{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
{{#notNullJacksonAnnotation}}@JsonInclude(JsonInclude.Include.NON_NULL){{/notNullJacksonAnnotation}}
@@ -34,12 +35,11 @@ public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}} {{#seriali
private {{{datatypeWithEnum}}} {{name}}{{#required}} = {{{defaultValue}}}{{/required}}{{^required}} = null{{/required}};
{{/isContainer}}
{{^isContainer}}
- private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};
+{{#useBeanValidation}}{{>beanValidationVariable}}{{/useBeanValidation}}{{^useBeanValidation}} private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}};{{/useBeanValidation}}
{{/isContainer}}
-
{{/vars}}
{{#vars}}
- public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
+{{#useBeanValidation}}{{>beanValidationFluentSetter}}{{/useBeanValidation}}{{^useBeanValidation}} public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { {{/useBeanValidation}}
this.{{name}} = {{name}};
return this;
}
@@ -86,20 +86,14 @@ public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}} {{#seriali
{{#vendorExtensions.extraAnnotation}}
{{{vendorExtensions.extraAnnotation}}}
{{/vendorExtensions.extraAnnotation}}
- {{#useOas2}}
- @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}")
- {{/useOas2}}
- {{^useOas2}}
- @Schema({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}accessMode = Schema.AccessMode.READ_ONLY, {{/isReadOnly}}description = "{{{description}}}")
- {{/useOas2}}
- {{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}} public {{{datatypeWithEnum}}} {{getter}}() {
+ {{#useOas2}}@ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/useOas2}}
+ {{^useOas2}}@Schema({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}accessMode = Schema.AccessMode.READ_ONLY, {{/isReadOnly}}description = "{{{description}}}"){{/useOas2}}
+ {{#useBeanValidation}}{{>beanValidationGetter}}{{/useBeanValidation}}{{^useBeanValidation}}public {{{datatypeWithEnum}}} {{getter}}() { {{/useBeanValidation}}
return {{name}};
}
-
- public void {{setter}}({{{datatypeWithEnum}}} {{name}}) {
+{{#useBeanValidation}}{{>beanValidationSetter}}{{/useBeanValidation}}{{^useBeanValidation}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { {{/useBeanValidation}}
this.{{name}} = {{name}};
}
-
{{/vars}}
@Override
diff --git a/src/test/java/io/swagger/codegen/v3/generators/java/SpringGeneratorTest.java b/src/test/java/io/swagger/codegen/v3/generators/java/SpringGeneratorTest.java
new file mode 100644
index 0000000000..0d66ade7c0
--- /dev/null
+++ b/src/test/java/io/swagger/codegen/v3/generators/java/SpringGeneratorTest.java
@@ -0,0 +1,115 @@
+package io.swagger.codegen.v3.generators.java;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.codegen.v3.generators.AbstractCodegenTest;
+import io.swagger.codegen.v3.service.GenerationRequest;
+import io.swagger.codegen.v3.service.GeneratorService;
+import io.swagger.codegen.v3.service.Options;
+import io.swagger.util.Json;
+import io.swagger.util.Yaml;
+import org.apache.commons.io.IOUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.List;
+
+public class SpringGeneratorTest extends AbstractCodegenTest {
+ @Test
+ public void testGenerator() throws Exception {
+
+ String path = getOutFolder(false).getAbsolutePath();
+ GenerationRequest request = new GenerationRequest();
+ request
+ .codegenVersion(GenerationRequest.CodegenVersion.V3) // use V2 to target Swagger/OpenAPI 2.x Codegen version
+ .type(GenerationRequest.Type.CLIENT)
+ .lang("spring")
+ // .specURL("https://petstore3.swagger.io/api/v3/openapi.yaml")
+ .spec(loadSpecAsNode( "3_0_0/nullable-required.yaml",
+ true, // YAML file, use false for JSON
+ false)) // OpenAPI 3.x - use true for Swagger/OpenAPI 2.x definitions
+ .options(
+ new Options()
+ // .addAdditionalProperty("validationMode", "loose")
+ // .addAdditionalProperty("validationMode", "legacy")
+ // .addAdditionalProperty("validationMode", "legacyNullable")
+ // .addAdditionalProperty("useBeanValidation", false)
+ // .addAdditionalProperty("useNullableForNotNull", false)
+ .outputDir(path)
+ );
+
+ List files = new GeneratorService().generationRequest(request).generate();
+ Assert.assertFalse(files.isEmpty());
+/* for (File f: files) {
+ // test stuff
+ if (f.getName().endsWith("Pet.java")) {
+ String content = new String(Files.readAllBytes(f.toPath()));
+ System.out.println(content);
+ }
+ if (f.getName().endsWith("application.properties")) {
+ String content = new String(Files.readAllBytes(f.toPath()));
+ System.out.println(content);
+ }
+ if (f.getName().endsWith("pom.xml")) {
+ String content = new String(Files.readAllBytes(f.toPath()));
+ // System.out.println(content);
+ }
+ if (f.getName().endsWith("Boot.java")) {
+ String content = new String(Files.readAllBytes(f.toPath()));
+ // System.out.println(content);
+ }
+ }*/
+ }
+
+ protected static File getTmpFolder() {
+ try {
+ File outputFolder = Files.createTempFile("codegentest-", "-tmp").toFile();
+ outputFolder.delete();
+ outputFolder.mkdir();
+ outputFolder.deleteOnExit();
+ return outputFolder;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ protected static File getOutFolder(boolean delete) {
+ try {
+ File outputFolder = getTmpFolder();
+
+ System.out.println(outputFolder.getAbsolutePath());
+ if (delete) {
+ // delete..
+ }
+ return outputFolder;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ protected JsonNode loadSpecAsNode(final String file, boolean yaml, boolean v2) {
+ InputStream in = null;
+ String s = "";
+ try {
+ in = getClass().getClassLoader().getResourceAsStream(file);
+ if (yaml) {
+ if (v2) {
+ return Yaml.mapper().readTree(in);
+ } else {
+ return io.swagger.v3.core.util.Yaml.mapper().readTree(in);
+ }
+ }
+ if (v2) {
+ return Json.mapper().readTree(in);
+ }
+ return io.swagger.v3.core.util.Json.mapper().readTree(in);
+ } catch (Exception e) {
+ throw new RuntimeException("could not load file " + file);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+}
diff --git a/src/test/resources/3_0_0/nullable-required.yaml b/src/test/resources/3_0_0/nullable-required.yaml
new file mode 100644
index 0000000000..c20a1d56b4
--- /dev/null
+++ b/src/test/resources/3_0_0/nullable-required.yaml
@@ -0,0 +1,44 @@
+openapi: 3.0.2
+info:
+ title: Nullable Required
+ version: 1.0.0
+servers:
+ - url: /api/v3
+paths:
+ /pet:
+ post:
+ operationId: addPet
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+ responses:
+ "200":
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+components:
+ schemas:
+ Pet:
+ required:
+ - notNullable_required
+ - nullable_required
+ type: object
+ properties:
+ nullable_notRequired:
+ type: string
+ example: doggie
+ nullable: true
+ notNullable_notRequired:
+ type: string
+ example: doggie
+ notNullable_required:
+ type: string
+ example: doggie
+ nullable_required:
+ type: string
+ example: doggie
+ nullable: true