Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4947][java]: adds support for validation of primitives in arrays #17165

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4889,7 +4889,15 @@ public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
}

CodegenProperty cp = fromProperty("response", responseSchema, false);
// the response data types should not contains a bean validation annotation.
if (cp.dataType.contains("@")) {
cp.dataType = cp.dataType.replaceAll("(?:(?i)@[a-z0-9]*+\\s*)*+", "");
}
r.dataType = getTypeDeclaration(responseSchema);
// the response data types should not contains a bean validation annotation.
if (r.dataType.contains("@")) {
r.dataType = r.dataType.replaceAll("(?:(?i)@[a-z0-9]*+\\s*)*+", "");
Aliaksie marked this conversation as resolved.
Show resolved Hide resolved
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's a must to do this in the default codegen as this code block seems to handle data type for Java generators only.

What about doing moving these check in postProcessWithModels in abstract java codegen instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure why not, please check if it looks better and i got you correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328, sorry to bother you, have you had a chance to look into it? do I need to make additional changes or can I resolve this conversation ?

r.returnProperty = cp;

if (!ModelUtils.isArraySchema(responseSchema)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import io.swagger.models.Model;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
Expand All @@ -35,6 +34,7 @@
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.languages.features.DocumentationProviderFeatures;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.ModelMap;
Expand Down Expand Up @@ -930,7 +930,7 @@ public String getTypeDeclaration(Schema p) {
Schema<?> target = ModelUtils.isGenerateAliasAsModel() ? p : schema;
if (ModelUtils.isArraySchema(target)) {
Schema<?> items = getSchemaItems((ArraySchema) schema);
return getSchemaType(target) + "<" + getTypeDeclaration(items) + ">";
return getSchemaType(target) + "<" + getBeanValidation(items) + getTypeDeclaration(items) + ">";
} else if (ModelUtils.isMapSchema(target)) {
// Note: ModelUtils.isMapSchema(p) returns true when p is a composed schema that also defines
// additionalproperties: true
Expand All @@ -945,6 +945,67 @@ public String getTypeDeclaration(Schema p) {
return super.getTypeDeclaration(target);
}

private String getBeanValidation(Schema<?> items) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor suggestion: add a docstring for this method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (Boolean.FALSE.equals(additionalProperties.getOrDefault(BeanValidationFeatures.USE_BEANVALIDATION, Boolean.FALSE))) {
return "";
}

if (ModelUtils.isTypeObjectSchema(items)) {
// prevents generation '@Valid' for Object
return "";
}

if (items.get$ref() != null) {
return "@Valid ";
}

if (StringUtils.isNotEmpty(items.getPattern()) && !ModelUtils.isByteArraySchema(items)) {
return String.format(Locale.ROOT, "@Pattern(regexp = \"%s\")", items.getPattern());
Aliaksie marked this conversation as resolved.
Show resolved Hide resolved
}

if (items.getMinLength() != null && items.getMaxLength() != null) {
return String.format(Locale.ROOT, "@Size(min = %d, max = %d)", items.getMinLength(), items.getMaxLength());
}

if (items.getMinLength() != null) {
return String.format(Locale.ROOT, "@Size(min = %d)", items.getMinLength());
}

if (items.getMaxLength() != null) {
return String.format(Locale.ROOT, "@Size(max = %d)", items.getMaxLength());
}

if (ModelUtils.isLongSchema(items) || ModelUtils.isIntegerSchema(items)) {
if (items.getMinimum() != null && items.getMaximum() != null) {
return String.format(Locale.ROOT, "@Min(%s) @Max(%s)", items.getMinimum(), items.getMaximum());
}

if (items.getMinimum() != null) {
return String.format(Locale.ROOT, "@Min(%s)", items.getMinimum());
}

if (items.getMaximum() != null) {
return String.format(Locale.ROOT, "@Max(%s)", items.getMaximum());
}
}

if (items.getMinimum() != null && items.getMaximum() != null) {
return String.format(Locale.ROOT, "@DecimalMin(value = \"%s\", inclusive = false) @DecimalMax(value = \"%s\", inclusive = false)",
items.getMinimum(),
items.getMaximum());
}

if (items.getMinimum() != null) {
return String.format(Locale.ROOT, "@DecimalMin( value = \"%s\", inclusive = false)", items.getMinimum());
}

if (items.getMaximum() != null) {
return String.format(Locale.ROOT, "@DecimalMax( value = \"%s\", inclusive = false)", items.getMaximum());
}

return "";
}

@Override
public String getAlias(String name) {
if (typeAliases != null && typeAliases.containsKey(name)) {
Expand Down Expand Up @@ -2289,7 +2350,7 @@ protected void handleImplicitHeaders(CodegenOperation operation) {
}
operation.hasParams = !operation.allParams.isEmpty();
}

private boolean shouldBeImplicitHeader(CodegenParameter parameter) {
return StringUtils.isNotBlank(implicitHeadersRegex) && parameter.baseName.matches(implicitHeadersRegex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1422,116 +1422,12 @@ public void setRequestMappingMode(RequestMappingMode requestMappingMode) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc spring technical committee as the change impacts spring codegen as well

@cachescrubber (2022/02) @welshm (2022/02) @MelleD (2022/02) @atextor (2022/02) @manedev79 (2022/02) @javisst (2022/02) @borsch (2022/02) @banlevente (2022/02) @Zomzog (2022/09) @martin-mfg (2023/08)

@Override
public CodegenParameter fromParameter( final Parameter parameter, final Set<String> imports ) {
CodegenParameter codegenParameter = super.fromParameter( parameter, imports );
if(!isListOrSet(codegenParameter)){
return codegenParameter;
}
codegenParameter.datatypeWithEnum = replaceBeanValidationCollectionType(codegenParameter.items, codegenParameter.datatypeWithEnum );
codegenParameter.dataType = replaceBeanValidationCollectionType(codegenParameter.items, codegenParameter.dataType );
return codegenParameter;
return super.fromParameter( parameter, imports );
}
@Override
public CodegenProperty fromProperty( String name, Schema p, boolean required, boolean schemaIsFromAdditionalProperties ) {
CodegenProperty codegenProperty = super.fromProperty( name, p, required, schemaIsFromAdditionalProperties );
if(!isListOrSet(codegenProperty)){
return codegenProperty;
}
codegenProperty.datatypeWithEnum = replaceBeanValidationCollectionType(codegenProperty.items, codegenProperty.datatypeWithEnum );
codegenProperty.dataType = replaceBeanValidationCollectionType(codegenProperty.items, codegenProperty.dataType );
return codegenProperty;
}

// The default validation applied for non-container and non-map types is sufficient for the SpringCodegen.
// Maps are very complex for bean validation, so it's currently not supported.
private static boolean isListOrSet(CodegenProperty codegenProperty) {
return codegenProperty.isContainer && !codegenProperty.isMap;
}

// The default validation applied for non-container and non-map types is sufficient for the SpringCodegen.
// Maps are very complex for bean validation, so it's currently not supported.
private static boolean isListOrSet(CodegenParameter codegenParameter) {
return codegenParameter.isContainer && !codegenParameter.isMap;
}

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 ");
return super.fromProperty( name, p, required, schemaIsFromAdditionalProperties );
}
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.isNotEmpty(codegenProperty.maximum)) {
return "@DecimalMax( value = \""+codegenProperty.maximum+"\", inclusive = false)";
}

return null;
}



public void setResourceFolder( String resourceFolder ) {
this.resourceFolder = resourceFolder;
Expand All @@ -1540,16 +1436,4 @@ public void setResourceFolder( String resourceFolder ) {
public String getResourceFolder() {
return resourceFolder;
}


// This should prevent, that the response data types not contains a @Valid annotation.
// However, the side effect is that attributes with response as name are also affected.
private static boolean isResponseType(CodegenProperty codegenProperty) {
return codegenProperty.baseName.toLowerCase(Locale.ROOT).contains("response");
}

// SPRING_HTTP_INTERFACE does not support bean validation.
public boolean useBeanValidation() {
return useBeanValidation && !SPRING_HTTP_INTERFACE.equals(library);
}
}
Loading