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 99baa6abec..ad71375852 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 @@ -67,10 +67,14 @@ import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.Negative; +import javax.validation.constraints.NegativeOrZero; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Positive; +import javax.validation.constraints.PositiveOrZero; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -1872,6 +1876,34 @@ protected boolean applyBeanValidatorAnnotations(Schema property, Annotation[] an modified = ValidationAnnotationsUtils.applyMaxConstraint(property, anno) || modified; } } + if (annos.containsKey(JAVAX_POSITIVE)) { + Positive anno = (Positive) annos.get(JAVAX_POSITIVE); + boolean apply = checkGroupValidation(anno.groups(), invocationGroups, acceptNoGroups); + if (apply) { + modified = ValidationAnnotationsUtils.applyPositiveConstraint(property, anno) || modified; + } + } + if (annos.containsKey(JAVAX_NEGATIVE)) { + Negative anno = (Negative) annos.get(JAVAX_NEGATIVE); + boolean apply = checkGroupValidation(anno.groups(), invocationGroups, acceptNoGroups); + if (apply) { + modified = ValidationAnnotationsUtils.applyNegativeConstraint(property, anno) || modified; + } + } + if (annos.containsKey(JAVAX_POSITIVE_OR_ZERO)) { + PositiveOrZero anno = (PositiveOrZero) annos.get(JAVAX_POSITIVE_OR_ZERO); + boolean apply = checkGroupValidation(anno.groups(), invocationGroups, acceptNoGroups); + if (apply) { + modified = ValidationAnnotationsUtils.applyPositiveOrZeroConstraint(property, anno) || modified; + } + } + if (annos.containsKey(JAVAX_NEGATIVE_OR_ZERO)) { + NegativeOrZero anno = (NegativeOrZero) annos.get(JAVAX_NEGATIVE_OR_ZERO); + boolean apply = checkGroupValidation(anno.groups(), invocationGroups, acceptNoGroups); + if (apply) { + modified = ValidationAnnotationsUtils.applyNegativeOrZeroConstraint(property, anno) || modified; + } + } if (annos.containsKey(JAVAX_SIZE)) { Size anno = (Size) annos.get(JAVAX_SIZE); boolean apply = checkGroupValidation(anno.groups(), invocationGroups, acceptNoGroups); @@ -1950,6 +1982,22 @@ protected boolean applyBeanValidatorAnnotationsNoGroups(Schema property, Annotat Max max = (Max) annos.get(JAVAX_MAX); modified = ValidationAnnotationsUtils.applyMaxConstraint(property, max) || modified; } + if (annos.containsKey(JAVAX_POSITIVE)) { + Positive positive = (Positive) annos.get(JAVAX_POSITIVE); + modified = ValidationAnnotationsUtils.applyPositiveConstraint(property, positive) || modified; + } + if (annos.containsKey(JAVAX_NEGATIVE)) { + Negative negative = (Negative) annos.get(JAVAX_NEGATIVE); + modified = ValidationAnnotationsUtils.applyNegativeConstraint(property, negative) || modified; + } + if (annos.containsKey(JAVAX_POSITIVE_OR_ZERO)) { + PositiveOrZero positiveOrZero = (PositiveOrZero) annos.get(JAVAX_POSITIVE_OR_ZERO); + modified = ValidationAnnotationsUtils.applyPositiveOrZeroConstraint(property, positiveOrZero) || modified; + } + if (annos.containsKey(JAVAX_NEGATIVE_OR_ZERO)) { + NegativeOrZero negativeOrZero = (NegativeOrZero) annos.get(JAVAX_NEGATIVE_OR_ZERO); + modified = ValidationAnnotationsUtils.applyNegativeOrZeroConstraint(property, negativeOrZero) || modified; + } if (annos.containsKey(JAVAX_SIZE)) { Size size = (Size) annos.get(JAVAX_SIZE); modified = ValidationAnnotationsUtils.applySizeConstraint(property, size) || modified; diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ValidationAnnotationsUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ValidationAnnotationsUtils.java index 3c9aad2c6e..84c47a2ef9 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ValidationAnnotationsUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ValidationAnnotationsUtils.java @@ -14,6 +14,10 @@ public class ValidationAnnotationsUtils { public static final String JAVAX_NOT_BLANK = "javax.validation.constraints.NotBlank"; public static final String JAVAX_MIN = "javax.validation.constraints.Min"; public static final String JAVAX_MAX = "javax.validation.constraints.Max"; + public static final String JAVAX_POSITIVE = "javax.validation.constraints.Positive"; + public static final String JAVAX_NEGATIVE = "javax.validation.constraints.Negative"; + public static final String JAVAX_POSITIVE_OR_ZERO = "javax.validation.constraints.PositiveOrZero"; + public static final String JAVAX_NEGATIVE_OR_ZERO = "javax.validation.constraints.NegativeOrZero"; public static final String JAVAX_SIZE = "javax.validation.constraints.Size"; public static final String JAVAX_DECIMAL_MIN = "javax.validation.constraints.DecimalMin"; public static final String JAVAX_DECIMAL_MAX = "javax.validation.constraints.DecimalMax"; @@ -91,6 +95,90 @@ public static boolean applyMaxConstraint(Schema schema, Max annotation) { return false; } + /** + * @param schema the schema + * @param annotation the schema's {@link Positive} annotation + * @return whether the schema has been modified or not + */ + public static boolean applyPositiveConstraint(Schema schema, Positive annotation) { + if (isNumberSchema(schema)) { + BigDecimal minimum = schema.getMinimum(); + if (minimum == null || minimum.compareTo(BigDecimal.ZERO) < 0) { + schema.setMinimum(BigDecimal.ZERO); + schema.setExclusiveMinimum(true); + return true; + } + if (minimum.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMinimum())) { + schema.setExclusiveMinimum(true); + return true; + } + } + return false; + } + + /** + * @param schema the schema + * @param annotation the schema's {@link Negative} annotation + * @return whether the schema has been modified or not + */ + public static boolean applyNegativeConstraint(Schema schema, Negative annotation) { + if (isNumberSchema(schema)) { + BigDecimal maximum = schema.getMaximum(); + if (maximum == null || maximum.compareTo(BigDecimal.ZERO) > 0) { + schema.setMaximum(BigDecimal.ZERO); + schema.setExclusiveMaximum(true); + return true; + } + if (maximum.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMaximum())) { + schema.setExclusiveMaximum(true); + return true; + } + } + return false; + } + + /** + * @param schema the schema + * @param annotation the schema's {@link PositiveOrZero} annotation + * @return whether the schema has been modified or not + */ + public static boolean applyPositiveOrZeroConstraint(Schema schema, PositiveOrZero annotation) { + if (isNumberSchema(schema)) { + BigDecimal minimum = schema.getMinimum(); + if (minimum == null || minimum.compareTo(BigDecimal.ZERO) < 0) { + schema.setMinimum(BigDecimal.ZERO); + schema.setExclusiveMinimum(false); + return true; + } + if (minimum.compareTo(BigDecimal.ZERO) == 0 && schema.getExclusiveMinimum() == null) { + schema.setExclusiveMinimum(false); + return true; + } + } + return false; + } + + /** + * @param schema the schema + * @param annotation the schema's {@link NegativeOrZero} annotation + * @return whether the schema has been modified or not + */ + public static boolean applyNegativeOrZeroConstraint(Schema schema, NegativeOrZero annotation) { + if (isNumberSchema(schema)) { + BigDecimal maximum = schema.getMaximum(); + if (maximum == null || maximum.compareTo(BigDecimal.ZERO) > 0) { + schema.setMaximum(BigDecimal.ZERO); + schema.setExclusiveMaximum(false); + return true; + } + if (maximum.compareTo(BigDecimal.ZERO) == 0 && schema.getExclusiveMaximum() == null) { + schema.setExclusiveMaximum(false); + return true; + } + } + return false; + } + /** * @param schema the schema * @param annotation the schema's {@link Size} annotation diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidationTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidationTest.java new file mode 100644 index 0000000000..ce2f2f58b5 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidationTest.java @@ -0,0 +1,130 @@ +package io.swagger.v3.core.resolving; + +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.media.Schema; +import org.testng.annotations.Test; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Negative; +import javax.validation.constraints.NegativeOrZero; +import javax.validation.constraints.Positive; +import javax.validation.constraints.PositiveOrZero; +import java.math.BigDecimal; +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +public class BeanValidationTest { + + @Test(description = "read javax sign constraints") + public void readJavaxSignConstraints() { + final Map schemas = ModelConverters.getInstance().readAll(JavaxSignConstraintsModel.class); + final Schema model = schemas.get("JavaxSignConstraintsModel"); + final Map properties = model.getProperties(); + + final Schema positive = properties.get("positive"); + assertEquals(positive.getMinimum(), BigDecimal.ZERO); + assertEquals(positive.getExclusiveMinimum(), Boolean.TRUE); + + final Schema negative = properties.get("negative"); + assertEquals(negative.getMaximum(), BigDecimal.ZERO); + assertEquals(negative.getExclusiveMaximum(), Boolean.TRUE); + + final Schema positiveOrZero = properties.get("positiveOrZero"); + assertEquals(positiveOrZero.getMinimum(), BigDecimal.ZERO); + assertEquals(positiveOrZero.getExclusiveMinimum(), Boolean.FALSE); + + final Schema negativeOrZero = properties.get("negativeOrZero"); + assertEquals(negativeOrZero.getMaximum(), BigDecimal.ZERO); + assertEquals(negativeOrZero.getExclusiveMaximum(), Boolean.FALSE); + } + + @Test(description = "read javax sign constraints with stronger min and max") + public void readJavaxSignConstraintsWithStrongerBounds() { + final Map schemas = ModelConverters.getInstance().readAll(JavaxSignAndRangeConstraintsModel.class); + final Schema model = schemas.get("JavaxSignAndRangeConstraintsModel"); + final Map properties = model.getProperties(); + + final Schema positiveWithMin = properties.get("positiveWithMin"); + assertEquals(positiveWithMin.getMinimum(), new BigDecimal("5")); + assertNull(positiveWithMin.getExclusiveMinimum()); + + final Schema negativeWithMax = properties.get("negativeWithMax"); + assertEquals(negativeWithMax.getMaximum(), new BigDecimal("-5")); + assertNull(negativeWithMax.getExclusiveMaximum()); + } + + public static class JavaxSignConstraintsModel { + @Positive + protected Integer positive; + + @Negative + protected Integer negative; + + @PositiveOrZero + protected Integer positiveOrZero; + + @NegativeOrZero + protected Integer negativeOrZero; + + public Integer getPositive() { + return positive; + } + + public void setPositive(Integer positive) { + this.positive = positive; + } + + public Integer getNegative() { + return negative; + } + + public void setNegative(Integer negative) { + this.negative = negative; + } + + public Integer getPositiveOrZero() { + return positiveOrZero; + } + + public void setPositiveOrZero(Integer positiveOrZero) { + this.positiveOrZero = positiveOrZero; + } + + public Integer getNegativeOrZero() { + return negativeOrZero; + } + + public void setNegativeOrZero(Integer negativeOrZero) { + this.negativeOrZero = negativeOrZero; + } + } + + public static class JavaxSignAndRangeConstraintsModel { + @Positive + @Min(5) + protected Integer positiveWithMin; + + @Negative + @Max(-5) + protected Integer negativeWithMax; + + public Integer getPositiveWithMin() { + return positiveWithMin; + } + + public void setPositiveWithMin(Integer positiveWithMin) { + this.positiveWithMin = positiveWithMin; + } + + public Integer getNegativeWithMax() { + return negativeWithMax; + } + + public void setNegativeWithMax(Integer negativeWithMax) { + this.negativeWithMax = negativeWithMax; + } + } +} diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/ValidationAnnotationsUtilsTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/ValidationAnnotationsUtilsTest.java index dacd153834..c196605a3e 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/util/ValidationAnnotationsUtilsTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/util/ValidationAnnotationsUtilsTest.java @@ -243,6 +243,102 @@ public Class annotationType() { }; } + private Positive createPositiveAnnotation() { + return new Positive() { + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public String message() { + return ""; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + + @Override + public Class annotationType() { + return Positive.class; + } + }; + } + + private Negative createNegativeAnnotation() { + return new Negative() { + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public String message() { + return ""; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + + @Override + public Class annotationType() { + return Negative.class; + } + }; + } + + private PositiveOrZero createPositiveOrZeroAnnotation() { + return new PositiveOrZero() { + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public String message() { + return ""; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + + @Override + public Class annotationType() { + return PositiveOrZero.class; + } + }; + } + + private NegativeOrZero createNegativeOrZeroAnnotation() { + return new NegativeOrZero() { + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public String message() { + return ""; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + + @Override + public Class annotationType() { + return NegativeOrZero.class; + } + }; + } + @Test public void testApplyNotEmptyConstraintOnArraySchema() { @@ -371,6 +467,130 @@ public void testApplyMaxConstraintOnStringSchema() { assertNull(schema.getMaximum()); } + @Test + public void testApplyPositiveConstraintOnNumberSchema() { + Schema schema = new NumberSchema(); + Positive positiveAnnotation = createPositiveAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema, positiveAnnotation); + + assertTrue(modified); + assertEquals(schema.getMinimum(), BigDecimal.ZERO); + assertTrue(schema.getExclusiveMinimum()); + } + + @Test + public void testApplyPositiveConstraintOnStringSchema() { + Schema schema = new StringSchema(); + Positive positiveAnnotation = createPositiveAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema, positiveAnnotation); + + assertFalse(modified); + assertNull(schema.getMinimum()); + assertNull(schema.getExclusiveMinimum()); + } + + @Test + public void testApplyPositiveConstraintDoesNotLoosenExistingMinimum() { + Schema schema = new NumberSchema(); + schema.setMinimum(new BigDecimal("5")); + schema.setExclusiveMinimum(false); + + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema, createPositiveAnnotation()); + + assertFalse(modified); + assertEquals(schema.getMinimum(), new BigDecimal("5")); + assertFalse(schema.getExclusiveMinimum()); + } + + @Test + public void testApplyNegativeConstraintOnNumberSchema() { + Schema schema = new NumberSchema(); + Negative negativeAnnotation = createNegativeAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema, negativeAnnotation); + + assertTrue(modified); + assertEquals(schema.getMaximum(), BigDecimal.ZERO); + assertTrue(schema.getExclusiveMaximum()); + } + + @Test + public void testApplyNegativeConstraintOnStringSchema() { + Schema schema = new StringSchema(); + Negative negativeAnnotation = createNegativeAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema, negativeAnnotation); + + assertFalse(modified); + assertNull(schema.getMaximum()); + assertNull(schema.getExclusiveMaximum()); + } + + @Test + public void testApplyNegativeConstraintDoesNotLoosenExistingMaximum() { + Schema schema = new NumberSchema(); + schema.setMaximum(new BigDecimal("-5")); + schema.setExclusiveMaximum(false); + + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema, createNegativeAnnotation()); + + assertFalse(modified); + assertEquals(schema.getMaximum(), new BigDecimal("-5")); + assertFalse(schema.getExclusiveMaximum()); + } + + @Test + public void testApplyPositiveOrZeroConstraintOnNumberSchema() { + Schema schema = new NumberSchema(); + PositiveOrZero positiveOrZeroAnnotation = createPositiveOrZeroAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyPositiveOrZeroConstraint(schema, positiveOrZeroAnnotation); + + assertTrue(modified); + assertEquals(schema.getMinimum(), BigDecimal.ZERO); + assertFalse(schema.getExclusiveMinimum()); + } + + @Test + public void testApplyPositiveOrZeroConstraintDoesNotLoosenExistingExclusiveMinimum() { + Schema schema = new NumberSchema(); + schema.setMinimum(BigDecimal.ZERO); + schema.setExclusiveMinimum(true); + + boolean modified = ValidationAnnotationsUtils.applyPositiveOrZeroConstraint(schema, createPositiveOrZeroAnnotation()); + + assertFalse(modified); + assertEquals(schema.getMinimum(), BigDecimal.ZERO); + assertTrue(schema.getExclusiveMinimum()); + } + + @Test + public void testApplyNegativeOrZeroConstraintOnNumberSchema() { + Schema schema = new NumberSchema(); + NegativeOrZero negativeOrZeroAnnotation = createNegativeOrZeroAnnotation(); + + boolean modified = ValidationAnnotationsUtils.applyNegativeOrZeroConstraint(schema, negativeOrZeroAnnotation); + + assertTrue(modified); + assertEquals(schema.getMaximum(), BigDecimal.ZERO); + assertFalse(schema.getExclusiveMaximum()); + } + + @Test + public void testApplyNegativeOrZeroConstraintDoesNotLoosenExistingExclusiveMaximum() { + Schema schema = new NumberSchema(); + schema.setMaximum(BigDecimal.ZERO); + schema.setExclusiveMaximum(true); + + boolean modified = ValidationAnnotationsUtils.applyNegativeOrZeroConstraint(schema, createNegativeOrZeroAnnotation()); + + assertFalse(modified); + assertEquals(schema.getMaximum(), BigDecimal.ZERO); + assertTrue(schema.getExclusiveMaximum()); + } + @Test public void testApplySizeConstraintOnNumberSchemaWithCustomValues() { Schema schema = new NumberSchema();