-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Changes from all commits
3f87820
8446e88
c668a11
49eb303
c932a6f
747dc06
e278537
bbecaf9
0810034
3b84e36
c7a83f4
e0f50bd
4ae5d42
77497ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -34,7 +33,9 @@ | |
import org.apache.commons.io.FilenameUtils; | ||
import org.apache.commons.lang3.BooleanUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.commons.text.StringEscapeUtils; | ||
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; | ||
|
@@ -930,7 +931,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 | ||
|
@@ -945,6 +946,128 @@ public String getTypeDeclaration(Schema p) { | |
return super.getTypeDeclaration(target); | ||
} | ||
|
||
/** | ||
* This method stand for resolve bean validation for container(array, set). | ||
* Return empty if there's no bean validation for requested type or prop useBeanValidation false or missed. | ||
* | ||
* @param items type | ||
* @return BeanValidation for declared type in container(array, set) | ||
*/ | ||
private String getBeanValidation(Schema<?> items) { | ||
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 (ModelUtils.isStringSchema(items)) { | ||
return getStringBeanValidation(items); | ||
} | ||
|
||
if (ModelUtils.isNumberSchema(items)) { | ||
return getNumberBeanValidation(items); | ||
} | ||
|
||
if (ModelUtils.isIntegerSchema(items)) { | ||
return getIntegerBeanValidation(items); | ||
} | ||
|
||
return ""; | ||
} | ||
|
||
private String getIntegerBeanValidation(Schema<?> items) { | ||
if (items.getMinimum() != null && items.getMaximum() != null) { | ||
return String.format(Locale.ROOT, "@Min(%s) @Max(%s)", items.getMinimum(), items.getMaximum()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is generating code - I don't think it should be taking in a locale, since it should be the same regardless of the machine/user Locale settings There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably better for @wing328 or someone with broader context to comment it just seems odd to use a Locale to format strings which are supposed to represent code. If that's the standard for the project, probably best to leave as-is. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let me take another look tomorrow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wing328 hey hey! sorry to bother you again... any updates? note: I noticed that the tests for some samples in the extended test checks did not pass due to the lack of imports .. that’s why I added imports to the templates .. I hope I found all the places where this is needed, let me know if it’s ok There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. checking now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some java sample tests still failed. can you please take a look when you've time? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh sorry for that, I missed that for helidon should use rootJavaEEPackage in templates, fixed hope now all tests will passed. Thanks! |
||
} | ||
|
||
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()); | ||
} | ||
return ""; | ||
} | ||
|
||
private String getNumberBeanValidation(Schema<?> items) { | ||
if (items.getMinimum() != null && items.getMaximum() != null) { | ||
return String.format(Locale.ROOT, "@DecimalMin(value = \"%s\", inclusive = %s) @DecimalMax(value = \"%s\", inclusive = %s)", | ||
items.getMinimum(), | ||
Optional.ofNullable(items.getExclusiveMinimum()).orElse(Boolean.FALSE), | ||
items.getMaximum(), | ||
Optional.ofNullable(items.getExclusiveMaximum()).orElse(Boolean.FALSE)); | ||
} | ||
|
||
if (items.getMinimum() != null) { | ||
return String.format(Locale.ROOT, "@DecimalMin( value = \"%s\", inclusive = %s)", | ||
items.getMinimum(), | ||
Optional.ofNullable(items.getExclusiveMinimum()).orElse(Boolean.FALSE)); | ||
} | ||
|
||
if (items.getMaximum() != null) { | ||
return String.format(Locale.ROOT, "@DecimalMax( value = \"%s\", inclusive = %s)", | ||
items.getMaximum(), | ||
Optional.ofNullable(items.getExclusiveMaximum()).orElse(Boolean.FALSE)); | ||
} | ||
|
||
return ""; | ||
} | ||
|
||
private String getStringBeanValidation(Schema<?> items) { | ||
String validations = ""; | ||
if (ModelUtils.isByteArraySchema(items) || ModelUtils.isBinarySchema(items)) { | ||
return validations; | ||
} | ||
|
||
if (StringUtils.isNotEmpty(items.getPattern())) { | ||
final String pattern = escapeUnsafeCharacters( | ||
StringEscapeUtils.unescapeJava( | ||
StringEscapeUtils.escapeJava(items.getPattern()) | ||
.replace("\\/", "/")) | ||
.replaceAll("[\\t\\n\\r]", " ") | ||
.replace("\\", "\\\\") | ||
.replace("\"", "\\\"")); | ||
|
||
validations = String.format(Locale.ROOT, "@Pattern(regexp = \"%s\")", pattern); | ||
} | ||
|
||
if (ModelUtils.isEmailSchema(items)) { | ||
return String.join("", "@Email "); | ||
} | ||
|
||
if (ModelUtils.isDecimalSchema(items)) { | ||
return String.join("", validations, getNumberBeanValidation(items)); | ||
} | ||
|
||
if (items.getMinLength() != null && items.getMaxLength() != null) { | ||
return String.join("", | ||
validations, | ||
String.format(Locale.ROOT, "@Size(min = %d, max = %d)", items.getMinLength(), items.getMaxLength())); | ||
} | ||
|
||
if (items.getMinLength() != null) { | ||
return String.join("", | ||
validations, | ||
String.format(Locale.ROOT, "@Size(min = %d)", items.getMinLength())); | ||
} | ||
|
||
if (items.getMaxLength() != null) { | ||
return String.join("", | ||
validations, | ||
String.format(Locale.ROOT, "@Size(max = %d)", items.getMaxLength())); | ||
} | ||
|
||
return validations; | ||
} | ||
|
||
@Override | ||
public String getAlias(String name) { | ||
if (typeAliases != null && typeAliases.containsKey(name)) { | ||
|
@@ -1511,6 +1634,21 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert | |
} | ||
} | ||
|
||
public void postProcessResponseWithProperty(CodegenResponse response, CodegenProperty property) { | ||
if (response == null || property == null || response.dataType == null || property.dataType == null) { | ||
return; | ||
} | ||
|
||
// the response data types should not contains a bean validation annotation. | ||
if (property.dataType.contains("@")) { | ||
property.dataType = property.dataType.replaceAll("(?:(?i)@[a-z0-9]*+\\s*)*+", ""); | ||
} | ||
// the response data types should not contains a bean validation annotation. | ||
if (response.dataType.contains("@")) { | ||
response.dataType = response.dataType.replaceAll("(?:(?i)@[a-z0-9]*+\\s*)*+", ""); | ||
} | ||
} | ||
|
||
@Override | ||
public ModelsMap postProcessModels(ModelsMap objs) { | ||
// recursively add import for mapping one type to multiple imports | ||
|
@@ -2289,7 +2427,7 @@ protected void handleImplicitHeaders(CodegenOperation operation) { | |
} | ||
operation.hasParams = !operation.allParams.isEmpty(); | ||
} | ||
|
||
private boolean shouldBeImplicitHeader(CodegenParameter parameter) { | ||
return StringUtils.isNotBlank(implicitHeadersRegex) && parameter.baseName.matches(implicitHeadersRegex); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1420,136 +1420,11 @@ public void setRequestMappingMode(RequestMappingMode requestMappingMode) { | |
this.requestMappingMode = requestMappingMode; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
@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 "); | ||
} | ||
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; | ||
} | ||
|
||
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); | ||
} | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done