forked from loafle/openapi-generator-original
Add bean validation for primitive container type 17450 (#17157)
* Add optional parameter for request body * Adapt Test * Add test * Format code * Remove extra method * Format code
This commit is contained in:
@@ -21,13 +21,22 @@ import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
|
||||
import static org.openapitools.codegen.utils.StringUtils.camelize;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.media.MediaType;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openapitools.codegen.CliOption;
|
||||
@@ -63,23 +72,9 @@ import org.openapitools.codegen.utils.URLPathUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.media.MediaType;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
|
||||
public class SpringCodegen extends AbstractJavaCodegen
|
||||
implements BeanValidationFeatures, PerformBeanValidationFeatures, OptionalFeatures, SwaggerUIFeatures {
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(SpringCodegen.class);
|
||||
|
||||
|
||||
public static final String TITLE = "title";
|
||||
public static final String SERVER_PORT = "serverPort";
|
||||
public static final String CONFIG_PACKAGE = "configPackage";
|
||||
@@ -1458,17 +1453,86 @@ public class SpringCodegen extends AbstractJavaCodegen
|
||||
return codegenParameter.isContainer && !codegenParameter.isMap;
|
||||
}
|
||||
|
||||
private String replaceBeanValidationCollectionType(CodegenProperty codegenProperty, String dataType) {
|
||||
if (!useBeanValidation() || !codegenProperty.isModel || isResponseType(codegenProperty)) {
|
||||
return dataType;
|
||||
private String replaceBeanValidationCollectionType(CodegenProperty codegenProperty, String dataType) {
|
||||
if (!useBeanValidation() || isResponseType(codegenProperty)) {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(dataType) || dataType.contains("@Valid")) {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
if (codegenProperty.isModel) {
|
||||
return dataType.replace("<", "<@Valid ");
|
||||
}
|
||||
String beanValidation = getPrimitiveBeanValidation(codegenProperty);
|
||||
if (beanValidation == null) {
|
||||
return dataType;
|
||||
}
|
||||
return dataType.replace("<", "<" + beanValidation + " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be in sync with beanValidationCore.mustache
|
||||
* @param codegenProperty the code property
|
||||
* @return the bean validation semantic for container primitive types
|
||||
*/
|
||||
private String getPrimitiveBeanValidation(CodegenProperty codegenProperty) {
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.pattern) && !codegenProperty.isByteArray) {
|
||||
return "@Pattern(regexp = \""+codegenProperty.pattern+"\")";
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty( dataType ) || dataType.contains( "@Valid" )) {
|
||||
return dataType;
|
||||
if (codegenProperty.minLength != null && codegenProperty.maxLength != null) {
|
||||
return "@Size(min = " + codegenProperty.minLength + ", max = " + codegenProperty.maxLength + ")";
|
||||
}
|
||||
return dataType.replace( "<", "<@Valid " );
|
||||
|
||||
if (codegenProperty.minLength != null) {
|
||||
return "@Size(min = " + codegenProperty.minLength + ")";
|
||||
}
|
||||
|
||||
if (codegenProperty.maxLength != null) {
|
||||
return "@Size(max = " + codegenProperty.maxLength + ")";
|
||||
}
|
||||
|
||||
|
||||
if (codegenProperty.isEmail) {
|
||||
return "@" + additionalProperties.get(JAVAX_PACKAGE)+".validation.constraints.Email";
|
||||
}
|
||||
|
||||
|
||||
if (codegenProperty.isLong || codegenProperty.isInteger) {
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.minimum) && StringUtils.isNotEmpty(codegenProperty.maximum)) {
|
||||
return "@Min("+codegenProperty.minimum+") @Max("+codegenProperty.maximum+")";
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.minimum)) {
|
||||
return "@Min("+codegenProperty.minimum+")";
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.maximum)) {
|
||||
return "@Max("+codegenProperty.maximum+")";
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.minimum) && StringUtils.isNotEmpty(codegenProperty.maximum)) {
|
||||
return "@DecimalMin(value = \""+codegenProperty.minimum+"\", inclusive = false) @DecimalMax(value = \""+codegenProperty.maximum+"\", inclusive = false)";
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.minimum)) {
|
||||
return "@DecimalMin( value = \""+codegenProperty.minimum+"\", inclusive = false)";
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(codegenProperty.maximum)) {
|
||||
return "@DecimalMax( value = \""+codegenProperty.maximum+"\", inclusive = false)";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setResourceFolder( String resourceFolder ) {
|
||||
this.resourceFolder = resourceFolder;
|
||||
}
|
||||
|
||||
@@ -893,6 +893,101 @@ public class SpringCodegenTest {
|
||||
.withType( "Set<Integer>" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldAddValidAnnotationIntoCollectionWhenBeanValidationIsEnabled_issue17150() throws IOException {
|
||||
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
|
||||
output.deleteOnExit();
|
||||
|
||||
OpenAPI openAPI = new OpenAPIParser()
|
||||
.readLocation("src/test/resources/3_0/spring/issue_17150.yaml", null, new ParseOptions()).getOpenAPI();
|
||||
SpringCodegen codegen = new SpringCodegen();
|
||||
codegen.setLibrary(SPRING_CLOUD_LIBRARY);
|
||||
codegen.setOutputDir(output.getAbsolutePath());
|
||||
codegen.additionalProperties().put(SpringCodegen.USE_BEANVALIDATION, "true");
|
||||
// codegen.additionalProperties().put(SpringCodegen.PERFORM_BEANVALIDATION, "true");
|
||||
codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.model");
|
||||
codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.controller");
|
||||
codegen.setUseSpringBoot3(true);
|
||||
|
||||
ClientOptInput input = new ClientOptInput()
|
||||
.openAPI(openAPI)
|
||||
.config(codegen);
|
||||
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
Map<String, File> files = generator.opts(input).generate().stream()
|
||||
.collect(Collectors.toMap(File::getName, Function.identity()));
|
||||
|
||||
JavaFileAssert.assertThat(files.get("Foo.java"))
|
||||
.isNormalClass()
|
||||
.hasImports("jakarta.validation.Valid")
|
||||
.hasImports("jakarta.validation.constraints")
|
||||
.hasProperty("stringPattern")
|
||||
.withType( "Set<@Pattern(regexp = \"[a-z]\") String>" )
|
||||
.toType()
|
||||
.hasProperty("stringMaxMinLength")
|
||||
.withType( "Set<@Size(min = 1, max = 10) String>" )
|
||||
.toType()
|
||||
.hasProperty("stringMinLength")
|
||||
.withType( "List<@Size(min = 1) String>" )
|
||||
.toType()
|
||||
.hasProperty("stringMaxLength")
|
||||
.withType( "Set<@Size(max = 1) String>" )
|
||||
.toType()
|
||||
.hasProperty("intMinMax")
|
||||
.withType( "List<@Min(1) @Max(10) Integer>" )
|
||||
.toType()
|
||||
.hasProperty("intMin")
|
||||
.withType( "List<@Min(1) Integer>" )
|
||||
.toType()
|
||||
.hasProperty("intMax")
|
||||
.withType( "List<@Max(10) Integer>" )
|
||||
.toType()
|
||||
.hasProperty("numberMinMax")
|
||||
.withType( "List<@DecimalMin(value = \"1\", inclusive = false) @DecimalMax(value = \"10\", inclusive = false) BigDecimal>" )
|
||||
.toType()
|
||||
.hasProperty("numberMin")
|
||||
.withType( "List<@DecimalMin(value = \"1\", inclusive = false) BigDecimal>" )
|
||||
.toType()
|
||||
.hasProperty("numberMax")
|
||||
.withType( "List<@DecimalMax(value = \"10\", inclusive = false) BigDecimal>" )
|
||||
.toType()
|
||||
|
||||
.hasProperty("stringPatternNullable")
|
||||
.withType( "JsonNullable<Set<@Pattern(regexp = \"[a-z]\") String>>" )
|
||||
.toType()
|
||||
.hasProperty("stringMaxMinLengthNullable")
|
||||
.withType( "JsonNullable<Set<@Size(min = 1, max = 10) String>>" )
|
||||
.toType()
|
||||
.hasProperty("stringMinLengthNullable")
|
||||
.withType( "JsonNullable<List<@Size(min = 1) String>>" )
|
||||
.toType()
|
||||
.hasProperty("stringMaxLengthNullable")
|
||||
.withType( "JsonNullable<Set<@Size(max = 1) String>>" )
|
||||
.toType()
|
||||
.hasProperty("intMinMaxNullable")
|
||||
.withType( "JsonNullable<List<@Min(1) @Max(10) Integer>>" )
|
||||
.toType()
|
||||
.hasProperty("intMinNullable")
|
||||
.withType( "JsonNullable<List<@Min(1) Integer>>" )
|
||||
.toType()
|
||||
.hasProperty("intMaxNullable")
|
||||
.withType( "JsonNullable<List<@Max(10) Integer>>" )
|
||||
.toType()
|
||||
.hasProperty("numberMinMaxNullable")
|
||||
.withType( "JsonNullable<List<@DecimalMin(value = \"1\", inclusive = false) @DecimalMax(value = \"10\", inclusive = false) BigDecimal>>" )
|
||||
.toType()
|
||||
.hasProperty("numberMinNullable")
|
||||
.withType( "JsonNullable<List<@DecimalMin(value = \"1\", inclusive = false) BigDecimal>>" )
|
||||
.toType()
|
||||
.hasProperty("numberMaxNullable")
|
||||
.withType( "JsonNullable<List<@DecimalMax(value = \"10\", inclusive = false) BigDecimal>>" )
|
||||
.toType()
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
// Helper function, intended to reduce boilerplate
|
||||
private Map<String, File> generateFiles(SpringCodegen codegen, String filePath) throws IOException {
|
||||
final File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Test Issue
|
||||
version: v1
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
responses:
|
||||
'200':
|
||||
description: default response
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/Foo'
|
||||
components:
|
||||
schemas:
|
||||
Foo:
|
||||
type: object
|
||||
properties:
|
||||
stringPattern:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
pattern: "[a-z]"
|
||||
stringMaxMinLength:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
maxItems: 10
|
||||
stringMinLength:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxItems: 10
|
||||
stringMaxLength:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1
|
||||
maxItems: 10
|
||||
|
||||
|
||||
stringEmail:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: email
|
||||
intMinMax:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
intMin:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
intMax:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
maximum: 10
|
||||
numberMinMax:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
numberMin:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 1
|
||||
numberMax:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
maximum: 10
|
||||
|
||||
stringPatternNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
pattern: "[a-z]"
|
||||
stringMaxMinLengthNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 10
|
||||
maxItems: 10
|
||||
stringMinLengthNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxItems: 10
|
||||
stringMaxLengthNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
uniqueItems: true
|
||||
items:
|
||||
type: string
|
||||
maxLength: 1
|
||||
maxItems: 10
|
||||
stringEmailNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: email
|
||||
intMinMaxNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
intMinNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
intMaxNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
maximum: 10
|
||||
numberMinMaxNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
numberMinNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
minimum: 1
|
||||
numberMaxNullable:
|
||||
nullable: true
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
maximum: 10
|
||||
Reference in New Issue
Block a user