From 74c49f5285aa7877bdada196d3442eedadffb97e Mon Sep 17 00:00:00 2001 From: Michael Clarke Date: Sat, 7 Dec 2024 21:36:02 +0000 Subject: [PATCH] #4804: Only include validation constraints with no groups by default The current process for adding constraints into the schema presumes that all javax or jakarta validation annotations enforce a constraint, and doesn't take into account the 'groups' property of the annotation. This results in constrains being added to the schema that would only be applied for certain request, so incorrectly marks fields as required in the resulting schema where they may only be mandatory on a subset of requests. To overcome this, a new `ValidationAnnotationFilter` is being introduced which only treats a validation annotation as constraining the schema where the conditions on the filter are met be the annotation. The default implementation of this filter only treats constraints from annotations with no groups as being enforced to bring it inline with how the default Java Beans validation operates. --- .../v3/core/converter/ModelConverters.java | 20 ++++- .../v3/core/jackson/ModelResolver.java | 82 +++++++++++++++---- .../jackson/ValidationAnnotationFilter.java | 60 ++++++++++++++ .../v3/core/util/ParameterProcessor.java | 17 +++- .../v3/core/resolving/Ticket4804Test.java | 74 +++++++++++++++++ .../v3/jaxrs2/DefaultParameterExtension.java | 15 +++- .../java/io/swagger/v3/jaxrs2/Reader.java | 2 + .../jaxrs2/ext/AbstractOpenAPIExtension.java | 7 ++ .../v3/jaxrs2/ext/OpenAPIExtension.java | 5 ++ .../swagger/v3/jaxrs2/util/ReaderUtils.java | 11 ++- 10 files changed, 268 insertions(+), 25 deletions(-) create mode 100644 modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ValidationAnnotationFilter.java create mode 100644 modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4804Test.java diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java index 843455e7d3..5fd0882073 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.type.TypeFactory; import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter.DefaultValidationAnnotationFilter; import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.Json31; import io.swagger.v3.oas.models.media.Schema; @@ -51,6 +53,16 @@ public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResoluti } } + public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution, ValidationAnnotationFilter validationAnnotationFilter) { + converters = new CopyOnWriteArrayList<>(); + if (openapi31) { + converters.add(new ModelResolver(Json31.mapper()).openapi31(true).schemaResolution(schemaResolution).validationAnnotationFilter(validationAnnotationFilter)); + } else { + converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution).validationAnnotationFilter(validationAnnotationFilter)); + } + } + + public Set getSkippedPackages() { return skippedPackages; } @@ -78,17 +90,21 @@ public static void reset() { } public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution) { + return getInstance(openapi31, schemaResolution, new DefaultValidationAnnotationFilter()); + } + + public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution, ValidationAnnotationFilter validationAnnotationFilter) { synchronized (ModelConverters.class) { if (openapi31) { if (SINGLETON31 == null) { boolean applySchemaResolution = Boolean.parseBoolean(System.getProperty(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY, "false")) || Boolean.parseBoolean(System.getenv(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY)); - SINGLETON31 = new ModelConverters(openapi31, applySchemaResolution ? schemaResolution : Schema.SchemaResolution.DEFAULT); + SINGLETON31 = new ModelConverters(openapi31, applySchemaResolution ? schemaResolution : Schema.SchemaResolution.DEFAULT, validationAnnotationFilter); init(SINGLETON31); } return SINGLETON31; } if (SINGLETON == null) { - SINGLETON = new ModelConverters(openapi31, schemaResolution); + SINGLETON = new ModelConverters(openapi31, schemaResolution, validationAnnotationFilter); init(SINGLETON); } return SINGLETON; diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index 2e5ea01248..20c0700e0b 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -31,6 +31,7 @@ import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.ModelConverter; import io.swagger.v3.core.converter.ModelConverterContext; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter.DefaultValidationAnnotationFilter; import io.swagger.v3.core.util.AnnotationsUtils; import io.swagger.v3.core.util.Constants; import io.swagger.v3.core.util.Json; @@ -56,7 +57,6 @@ import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.JsonSchema; import io.swagger.v3.oas.models.media.MapSchema; -import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; @@ -71,6 +71,9 @@ import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlAccessType; @@ -111,6 +114,11 @@ public class ModelResolver extends AbstractModelConverter implements ModelConverter { Logger LOGGER = LoggerFactory.getLogger(ModelResolver.class); + + /** + * @deprecated Use {@link ModelResolver#hasNotNullAnnotation(Annotation[], ValidationAnnotationFilter)} instead + */ + @Deprecated public static List NOT_NULL_ANNOTATIONS = Arrays.asList("NotNull", "NonNull", "NotBlank", "NotEmpty"); public static final String SET_PROPERTY_OF_COMPOSED_MODEL_AS_SIBLING = "composed-model-properties-as-sibiling"; @@ -125,6 +133,8 @@ public class ModelResolver extends AbstractModelConverter implements ModelConver private boolean openapi31; + private ValidationAnnotationFilter validationAnnotationFilter = new DefaultValidationAnnotationFilter(); + private Schema.SchemaResolution schemaResolution = Schema.SchemaResolution.DEFAULT; public ModelResolver(ObjectMapper mapper) { @@ -1574,57 +1584,56 @@ protected void applyBeanValidatorAnnotations(Schema property, Annotation[] annot } } if (parent != null && annotations != null && applyNotNullAnnotations) { - boolean requiredItem = Arrays.stream(annotations).anyMatch(annotation -> - NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName()) - ); + boolean requiredItem = hasNotNullAnnotation(annotations, validationAnnotationFilter); if (requiredItem) { addRequiredItem(parent, property.getName()); } } if (annos.containsKey("javax.validation.constraints.Min")) { - if (isNumberSchema(property)) { - Min min = (Min) annos.get("javax.validation.constraints.Min"); + Min min = (Min) annos.get("javax.validation.constraints.Min"); + if (isNumberSchema(property) && validationAnnotationFilter.isMinAnnotationApplicable(min)) { property.setMinimum(new BigDecimal(min.value())); } } if (annos.containsKey("javax.validation.constraints.Max")) { - if (isNumberSchema(property)) { - Max max = (Max) annos.get("javax.validation.constraints.Max"); + Max max = (Max) annos.get("javax.validation.constraints.Max"); + if (isNumberSchema(property) && validationAnnotationFilter.isMaxAnnotationApplicable(max)) { property.setMaximum(new BigDecimal(max.value())); } } if (annos.containsKey("javax.validation.constraints.Size")) { Size size = (Size) annos.get("javax.validation.constraints.Size"); - if (isNumberSchema(property)) { + boolean isApplicable = validationAnnotationFilter.isSizeAnnotationApplicable(size); + if (isApplicable && isNumberSchema(property)) { property.setMinimum(new BigDecimal(size.min())); property.setMaximum(new BigDecimal(size.max())); } - if (isStringSchema(property)) { + if (isApplicable && isStringSchema(property)) { property.setMinLength(Integer.valueOf(size.min())); property.setMaxLength(Integer.valueOf(size.max())); } - if (isArraySchema(property)) { + if (isApplicable && isArraySchema(property)) { property.setMinItems(size.min()); property.setMaxItems(size.max()); } } if (annos.containsKey("javax.validation.constraints.DecimalMin")) { DecimalMin min = (DecimalMin) annos.get("javax.validation.constraints.DecimalMin"); - if (isNumberSchema(property)) { + if (isNumberSchema(property) && validationAnnotationFilter.isDecimalMinAnnotationApplicable(min)) { property.setMinimum(new BigDecimal(min.value())); property.setExclusiveMinimum(!min.inclusive()); } } if (annos.containsKey("javax.validation.constraints.DecimalMax")) { DecimalMax max = (DecimalMax) annos.get("javax.validation.constraints.DecimalMax"); - if (isNumberSchema(property)) { + if (isNumberSchema(property) && validationAnnotationFilter.isDecimalMaxAnnotationApplicable(max)) { property.setMaximum(new BigDecimal(max.value())); property.setExclusiveMaximum(!max.inclusive()); } } if (annos.containsKey("javax.validation.constraints.Pattern")) { Pattern pattern = (Pattern) annos.get("javax.validation.constraints.Pattern"); - if (isStringSchema(property)) { + if (isStringSchema(property) && validationAnnotationFilter.isPatternAnnotationApplicable(pattern)) { property.setPattern(pattern.regexp()); } if(property.getItems() != null && isStringSchema(property.getItems())) { @@ -2416,9 +2425,9 @@ protected Set resolveIgnoredProperties(Annotations a, Annotation[] annot Set propertiesToIgnore = new HashSet<>(); JsonIgnoreProperties ignoreProperties = a.get(JsonIgnoreProperties.class); if (ignoreProperties != null) { - if(!ignoreProperties.allowGetters()) { - propertiesToIgnore.addAll(Arrays.asList(ignoreProperties.value())); - } + if(!ignoreProperties.allowGetters()) { + propertiesToIgnore.addAll(Arrays.asList(ignoreProperties.value())); + } } propertiesToIgnore.addAll(resolveIgnoredProperties(annotations)); return propertiesToIgnore; @@ -3079,6 +3088,11 @@ public ModelResolver openapi31(boolean openapi31) { return this; } + public ModelResolver validationAnnotationFilter(ValidationAnnotationFilter validationAnnotationFilter) { + this.validationAnnotationFilter = validationAnnotationFilter; + return this; + } + public boolean isOpenapi31() { return openapi31; } @@ -3126,4 +3140,38 @@ protected boolean applySchemaResolution() { (Boolean.parseBoolean(System.getProperty(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY, "false")) || Boolean.parseBoolean(System.getenv(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY))); } + + + /** + * Check if the parameter has any of the annotations that make it non-optional + * + * @param annotations the annotations to check + * @param validationAnnotationFilter the filters to use to remove annotations that don't match the current group + * @return whether any of the known NotNull annotations are present + */ + public static boolean hasNotNullAnnotation(Annotation[] annotations, ValidationAnnotationFilter validationAnnotationFilter) { + for (Annotation annotation : annotations) { + if (hasNotNullAnnotation(annotation, validationAnnotationFilter)) { + return true; + } + } + return false; + } + + public static boolean hasNotNullAnnotation(Annotation annotation, ValidationAnnotationFilter validationAnnotationFilter) { + if (annotation instanceof NotEmpty && validationAnnotationFilter.isNotEmptyAnnotationApplicable((NotEmpty) annotation)) { + return true; + } + if (annotation instanceof NotBlank && validationAnnotationFilter.isNotBlankAnnotationApplicable((NotBlank) annotation)) { + return true; + } + if (annotation instanceof NotNull && validationAnnotationFilter.isNotNullAnnotationApplicable((NotNull) annotation)) { + return true; + } + if (annotation.annotationType().getSimpleName().equals("NonNull")) { + return true; + } + return false; + } + } diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ValidationAnnotationFilter.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ValidationAnnotationFilter.java new file mode 100644 index 0000000000..a01e12e73d --- /dev/null +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ValidationAnnotationFilter.java @@ -0,0 +1,60 @@ +package io.swagger.v3.core.jackson; + +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +public interface ValidationAnnotationFilter { + + default boolean isNotEmptyAnnotationApplicable(NotEmpty notEmpty) { + return annotationGroupMatches(notEmpty.groups()); + } + + default boolean isNotBlankAnnotationApplicable(NotBlank notBlank) { + return annotationGroupMatches(notBlank.groups()); + } + + default boolean isNotNullAnnotationApplicable(NotNull notNull) { + return annotationGroupMatches(notNull.groups()); + } + + default boolean isMaxAnnotationApplicable(Max max) { + return annotationGroupMatches(max.groups()); + } + + default boolean isMinAnnotationApplicable(Min min) { + return annotationGroupMatches(min.groups()); + } + + default boolean isDecimalMaxAnnotationApplicable(DecimalMax decimalMax) { + return annotationGroupMatches(decimalMax.groups()); + } + + default boolean isDecimalMinAnnotationApplicable(DecimalMin decimalMin) { + return annotationGroupMatches(decimalMin.groups()); + } + + default boolean isSizeAnnotationApplicable(Size size) { + return annotationGroupMatches(size.groups()); + } + + default boolean isPatternAnnotationApplicable(Pattern pattern) { + return annotationGroupMatches(pattern.groups()); + } + + boolean annotationGroupMatches(Class[] annotationGroups); + + class DefaultValidationAnnotationFilter implements ValidationAnnotationFilter { + + @Override + public boolean annotationGroupMatches(Class[] annotationGroups) { + return annotationGroups.length == 0; + } + } +} diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java index 18e1277328..6971f7318e 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java @@ -5,6 +5,8 @@ import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.core.converter.ResolvedSchema; import io.swagger.v3.core.jackson.ModelResolver; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter.DefaultValidationAnnotationFilter; import io.swagger.v3.oas.annotations.enums.Explode; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.models.Components; @@ -55,7 +57,20 @@ public static Parameter applyAnnotations( JsonView jsonViewAnnotation, boolean openapi31, Schema.SchemaResolution schemaResolution) { + return applyAnnotations(parameter, type, annotations, components, classTypes, methodTypes, jsonViewAnnotation, openapi31, schemaResolution, new DefaultValidationAnnotationFilter()); + } + public static Parameter applyAnnotations( + Parameter parameter, + Type type, + List annotations, + Components components, + String[] classTypes, + String[] methodTypes, + JsonView jsonViewAnnotation, + boolean openapi31, + Schema.SchemaResolution schemaResolution, + ValidationAnnotationFilter validationAnnotationFilter) { final AnnotationsHelper helper = new AnnotationsHelper(annotations, type); if (helper.isContext()) { return null; @@ -252,7 +267,7 @@ public static Parameter applyAnnotations( } catch (Exception e) { LOGGER.error("failed on " + annotation.annotationType().getName(), e); } - } else if (ModelResolver.NOT_NULL_ANNOTATIONS.contains(annotation.annotationType().getSimpleName())) { + } else if (ModelResolver.hasNotNullAnnotation(annotation, validationAnnotationFilter)) { parameter.setRequired(true); } } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4804Test.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4804Test.java new file mode 100644 index 0000000000..200b5a7970 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/Ticket4804Test.java @@ -0,0 +1,74 @@ +package io.swagger.v3.core.resolving; + +import java.math.BigDecimal; +import java.util.Arrays; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.core.converter.ResolvedSchema; +import io.swagger.v3.core.matchers.SerializationMatchers; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.Schema.SchemaResolution; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class Ticket4804Test { + + @BeforeMethod + public void beforeTest() { + ModelConverters.reset(); + System.clearProperty(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY); + } + + @Test + public void shouldIncludeOnlyNonGroupedJakartaValidatedFieldsAsMandatoryByDefault() { + ResolvedSchema schema = ModelConverters.getInstance(false).resolveAsResolvedSchema(new AnnotatedType().type(CustomClass.class)); + String expectedJson = "{\"schema\":{\"required\":[\"nonGroupValidatedField\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}},\"referencedSchemas\":{\"CustomClass\":{\"required\":[\"nonGroupValidatedField\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}}}}"; + SerializationMatchers.assertEqualsToJson(schema, expectedJson); + } + + @Test + public void shouldIncludeAllJakartaValidatedFieldsAsMandatoryIfFilterAlwaysTrue() { + ResolvedSchema schema = ModelConverters.getInstance(false, SchemaResolution.DEFAULT, annotationGroups -> true).resolveAsResolvedSchema(new AnnotatedType().type(CustomClass.class)); + String expectedJson = "{\"schema\":{\"required\":[\"nonGroupValidatedField\",\"singleGroupValidatedField2\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"minimum\":1,\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"minimum\":1,\"exclusiveMinimum\":false,\"type\":\"number\"},\"otherGroupValidatedField\":{\"pattern\":\".*\",\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}},\"referencedSchemas\":{\"CustomClass\":{\"required\":[\"nonGroupValidatedField\",\"singleGroupValidatedField2\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"minimum\":1,\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"minimum\":1,\"exclusiveMinimum\":false,\"type\":\"number\"},\"otherGroupValidatedField\":{\"pattern\":\".*\",\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}}}}"; + SerializationMatchers.assertEqualsToJson(schema, expectedJson); + } + + + @Test + public void shouldIncludeOnlyJakartaValidatedFieldsMatchingConditionInFilter() { + ResolvedSchema schema = ModelConverters.getInstance(false, SchemaResolution.ALL_OF, annotationGroups -> annotationGroups.length == 0 || Arrays.stream(annotationGroups).anyMatch(group -> group == ValidationGroup.class)).resolveAsResolvedSchema(new AnnotatedType().type(CustomClass.class)); + String expectedJson = "{\"schema\":{\"required\":[\"nonGroupValidatedField\",\"singleGroupValidatedField2\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"minimum\":1,\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"minimum\":1,\"exclusiveMinimum\":false,\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}},\"referencedSchemas\":{\"CustomClass\":{\"required\":[\"nonGroupValidatedField\",\"singleGroupValidatedField2\"],\"type\":\"object\",\"properties\":{\"nonGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField\":{\"minimum\":1,\"type\":\"integer\",\"format\":\"int32\"},\"multipleGroupValidatedField\":{\"minimum\":1,\"exclusiveMinimum\":false,\"type\":\"number\"},\"otherGroupValidatedField\":{\"type\":\"string\"},\"singleGroupValidatedField2\":{\"type\":\"string\"}}}}}"; + SerializationMatchers.assertEqualsToJson(schema, expectedJson); + } + + + private interface ValidationGroup { + + } + + private interface OtherValidationGroup { + + } + + + private static class CustomClass { + + @NotNull + public String nonGroupValidatedField; + @Min(value = 1, groups = ValidationGroup.class) + public Integer singleGroupValidatedField; + @DecimalMin(value = "1.0", groups = {ValidationGroup.class, OtherValidationGroup.class}) + public BigDecimal multipleGroupValidatedField; + @Pattern(regexp = ".*", groups = OtherValidationGroup.class) + public String otherGroupValidatedField; + @NotEmpty(groups = ValidationGroup.class) + public String singleGroupValidatedField2; + } +} diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java index 541dda6b52..f9445fa95d 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/DefaultParameterExtension.java @@ -120,7 +120,11 @@ public ResolvedParameter extractParameters(List annotations, annotations, components, classConsumes == null ? new String[0] : classConsumes.value(), - methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31, this.schemaResolution); + methodConsumes == null ? new String[0] : methodConsumes.value(), + jsonViewAnnotation, + openapi31, + this.schemaResolution, + validationAnnotationFilter); if (unknownParameter != null) { if (StringUtils.isNotBlank(unknownParameter.getIn()) && !"form".equals(unknownParameter.getIn())) { extractParametersResult.parameters.add(unknownParameter); @@ -142,7 +146,8 @@ public ResolvedParameter extractParameters(List annotations, methodConsumes == null ? new String[0] : methodConsumes.value(), jsonViewAnnotation, openapi31, - this.schemaResolution); + this.schemaResolution, + validationAnnotationFilter); if (processedParameter != null) { extractParametersResult.parameters.add(processedParameter); } @@ -267,7 +272,8 @@ private boolean handleAdditionalAnnotation(List parameters, List parameters, List annotation extension.setOpenAPI31(Boolean.TRUE.equals(config.isOpenAPI31())); Schema.SchemaResolution curSchemaResolution = config.getSchemaResolution(); extension.setSchemaResolution(config.getSchemaResolution()); + extension.setValidationAnnotationFilter(new DefaultValidationAnnotationFilter()); ResolvedParameter resolvedParameter = extension.extractParameters(annotations, type, typesToSkip, components, classConsumes, methodConsumes, true, jsonViewAnnotation, chain); ((SwaggerConfiguration)config).setSchemaResolution(curSchemaResolution); return resolvedParameter; diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java index 197e248b24..b0c4b7c30e 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/AbstractOpenAPIExtension.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.TypeFactory; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter; import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; @@ -19,6 +20,7 @@ public abstract class AbstractOpenAPIExtension implements OpenAPIExtension { protected boolean openapi31; protected Schema.SchemaResolution schemaResolution; + protected ValidationAnnotationFilter validationAnnotationFilter; @Override public String extractOperationMethod(Method method, Iterator chain) { @@ -75,4 +77,9 @@ public void setOpenAPI31(boolean openapi31) { public void setSchemaResolution(Schema.SchemaResolution schemaResolution) { this.schemaResolution = schemaResolution; } + + @Override + public void setValidationAnnotationFilter(ValidationAnnotationFilter validationAnnotationFilter) { + this.validationAnnotationFilter = validationAnnotationFilter; + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java index b82e8b581d..cdebd401c8 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/ext/OpenAPIExtension.java @@ -1,6 +1,7 @@ package io.swagger.v3.jaxrs2.ext; import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter; import io.swagger.v3.jaxrs2.ResolvedParameter; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; @@ -36,4 +37,8 @@ default void setOpenAPI31(boolean openapi31) { default void setSchemaResolution(Schema.SchemaResolution schemaResolution) { //todo: override me! } + + default void setValidationAnnotationFilter(ValidationAnnotationFilter validationAnnotationFilter) { + //todo: override me! + } } diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java index 581bc0bbb4..40d44b256d 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/util/ReaderUtils.java @@ -1,6 +1,8 @@ package io.swagger.v3.jaxrs2.util; import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter; +import io.swagger.v3.core.jackson.ValidationAnnotationFilter.DefaultValidationAnnotationFilter; import io.swagger.v3.core.util.ParameterProcessor; import io.swagger.v3.core.util.ReflectionUtils; import io.swagger.v3.jaxrs2.ext.OpenAPIExtension; @@ -47,6 +49,10 @@ public static List collectConstructorParameters(Class cls, Compone * @return the collection of supported parameters */ public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation, Schema.SchemaResolution schemaResolution) { + return collectConstructorParameters(cls, components, classConsumes, jsonViewAnnotation, schemaResolution, new DefaultValidationAnnotationFilter()); + } + + public static List collectConstructorParameters(Class cls, Components components, javax.ws.rs.Consumes classConsumes, JsonView jsonViewAnnotation, Schema.SchemaResolution schemaResolution, ValidationAnnotationFilter validationAnnotationFilter) { if (cls.isLocalClass() || (cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()))) { return Collections.emptyList(); } @@ -81,7 +87,10 @@ public static List collectConstructorParameters(Class cls, Compone components, classConsumes == null ? new String[0] : classConsumes.value(), null, - jsonViewAnnotation, false, schemaResolution); + jsonViewAnnotation, + false, + schemaResolution, + validationAnnotationFilter); if (processedParameter != null) { parameters.add(processedParameter); }