Skip to content

Commit

Permalink
Add bean validation for primitive container type 17450 (OpenAPITools#…
Browse files Browse the repository at this point in the history
…17157)

* Add optional parameter for request body

* Adapt Test

* Add test

* Format code

* Remove extra method

* Format code
  • Loading branch information
MelleD authored Nov 30, 2023
1 parent 4c4d0e4 commit 939ffdd
Show file tree
Hide file tree
Showing 3 changed files with 341 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@
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;
Expand Down Expand Up @@ -63,23 +72,9 @@
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";
Expand Down Expand Up @@ -1458,17 +1453,86 @@ private static boolean isListOrSet(CodegenParameter codegenParameter) {
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 (codegenProperty.minLength != null && codegenProperty.maxLength != null) {
return "@Size(min = " + codegenProperty.minLength + ", max = " + codegenProperty.maxLength + ")";
}

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.isEmpty( dataType ) || dataType.contains( "@Valid" )) {
return dataType;
if (StringUtils.isNotEmpty(codegenProperty.maximum)) {
return "@DecimalMax( value = \""+codegenProperty.maximum+"\", inclusive = false)";
}
return dataType.replace( "<", "<@Valid " );

return null;
}



public void setResourceFolder( String resourceFolder ) {
this.resourceFolder = resourceFolder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,101 @@ public void shouldAddValidAnnotationIntoCollectionWhenBeanValidationIsEnabled_is
.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();
Expand Down
Loading

0 comments on commit 939ffdd

Please sign in to comment.