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 468349daf0..b0895c33c3 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 @@ -1,5 +1,6 @@ package io.swagger.v3.core.util; +import io.swagger.v3.oas.models.SpecVersion; import io.swagger.v3.oas.models.media.Schema; import javax.validation.constraints.*; @@ -184,11 +185,21 @@ public static boolean applyEmailConstraint(Schema schema, Email annotation) { public static boolean applyPositiveConstraint(Schema schema) { if (isNumberSchema(schema)) { BigDecimal current = schema.getMinimum(); - if (current == null || current.compareTo(BigDecimal.ZERO) < 0) { - schema.setMinimum(BigDecimal.ZERO); - schema.setExclusiveMinimum(true); - } else if (current.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMinimum())) { - schema.setExclusiveMinimum(true); + if (schema.getSpecVersion().equals(SpecVersion.V30)) { + if (currentMinimumOutsidePositiveRange(current)) { + schema.setMinimum(BigDecimal.ZERO); + schema.setExclusiveMinimum(true); + } else if (current.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMinimum())) { + schema.setExclusiveMinimum(true); + } + } else { + // OpenAPI 3.1: use exclusiveMinimumValue as a numeric value + if (schema.getExclusiveMinimumValue() == null && currentMinimumOutsidePositiveRange(current)) { + schema.setExclusiveMinimumValue(BigDecimal.ZERO); + return true; + } + // If currentExclusive is already 0 or positive, keep the stricter bound + return false; } return true; } @@ -209,11 +220,21 @@ public static boolean applyPositiveOrZeroConstraint(Schema schema) { public static boolean applyNegativeConstraint(Schema schema) { if (isNumberSchema(schema)) { BigDecimal current = schema.getMaximum(); - if (current == null || current.compareTo(BigDecimal.ZERO) > 0) { - schema.setMaximum(BigDecimal.ZERO); - schema.setExclusiveMaximum(true); - } else if (current.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMaximum())) { - schema.setExclusiveMaximum(true); + if (schema.getSpecVersion().equals(SpecVersion.V30)) { + if (currentMaximumOutsideNegativeRange(current)) { + schema.setMaximum(BigDecimal.ZERO); + schema.setExclusiveMaximum(true); + } else if (current.compareTo(BigDecimal.ZERO) == 0 && !Boolean.TRUE.equals(schema.getExclusiveMaximum())) { + schema.setExclusiveMaximum(true); + } + } else { + // OpenAPI 3.1: use exclusiveMaximumValue as a numeric value + if (schema.getExclusiveMaximumValue() == null && currentMaximumOutsideNegativeRange(current)) { + schema.setExclusiveMaximumValue(BigDecimal.ZERO); + return true; + } + // If currentExclusive is already 0 or negative, keep the stricter bound + return false; } return true; } @@ -231,4 +252,12 @@ public static boolean applyNegativeOrZeroConstraint(Schema schema) { return false; } + private static boolean currentMinimumOutsidePositiveRange(BigDecimal currentMinimum) { + return currentMinimum == null || currentMinimum.compareTo(BigDecimal.ZERO) < 0; + } + + private static boolean currentMaximumOutsideNegativeRange(BigDecimal currentMaximum) { + return currentMaximum == null || currentMaximum.compareTo(BigDecimal.ZERO) > 0; + } + } diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidatorTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidatorTest.java index f582b0d35e..23897bf81f 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidatorTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/BeanValidatorTest.java @@ -2,12 +2,7 @@ import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.core.oas.models.BeanValidationsModel; -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.EmailSchema; -import io.swagger.v3.oas.models.media.IntegerSchema; -import io.swagger.v3.oas.models.media.NumberSchema; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.media.*; import org.testng.annotations.Test; import java.math.BigDecimal; @@ -20,7 +15,7 @@ public class BeanValidatorTest { @Test(description = "read bean validations") - public void readBeanValidatorTest() { + public void readBeanValidatorOAS30Test() { final Map schemas = ModelConverters.getInstance().readAll(BeanValidationsModel.class); final Schema model = schemas.get("BeanValidationsModel"); final Map properties = model.getProperties(); @@ -94,4 +89,83 @@ public void readBeanValidatorTest() { assertEquals(negativeOrZeroWithMax.getMaximum(), new BigDecimal("-2")); assertNull(negativeOrZeroWithMax.getExclusiveMaximum()); } + + @Test(description = "read bean validations") + public void readBeanValidatorOAS31Test() { + final Map schemas = ModelConverters.getInstance(true).readAll(BeanValidationsModel.class); + final Schema model = schemas.get("BeanValidationsModel"); + final Map properties = model.getProperties(); + + assertTrue(model.getRequired().contains("id")); + assertTrue(model.getRequired().contains("username")); + + final JsonSchema username = (JsonSchema) properties.get("username"); + assertEquals(username.getPattern(), "(?![-._])[-._a-zA-Z0-9]{3,32}"); + + final JsonSchema age = (JsonSchema) properties.get("age"); + assertEquals(age.getMinimum(), new BigDecimal(13.0)); + assertEquals(age.getMaximum(), new BigDecimal(99.0)); + + final JsonSchema password = (JsonSchema) properties.get("password"); + assertEquals((int) password.getMinLength(), 6); + assertEquals((int) password.getMaxLength(), 20); + + final JsonSchema email = (JsonSchema) properties.get("email"); + assertEquals(email.getFormat(), "email"); + + final JsonSchema minBalance = (JsonSchema) properties.get("minBalance"); + assertTrue(minBalance.getExclusiveMinimum()); + + final JsonSchema maxBalance = (JsonSchema) properties.get("maxBalance"); + assertTrue(maxBalance.getExclusiveMaximum()); + + final JsonSchema items = (JsonSchema) properties.get("items"); + assertEquals((int) items.getMinItems(), 2); + assertEquals((int) items.getMaxItems(), 10); + assertEquals((int) items.getItems().getMinLength(), 3); + assertEquals((int) items.getItems().getMaxLength(), 4); + + final JsonSchema optionalValue = (JsonSchema) properties.get("optionalValue"); + assertEquals((int) optionalValue.getMinLength(), 1); + assertEquals((int) optionalValue.getMaxLength(), 10); + + final JsonSchema positiveAmount = (JsonSchema) properties.get("positiveAmount"); + assertEquals(positiveAmount.getExclusiveMinimumValue(), BigDecimal.ZERO); + + final JsonSchema positiveOrZeroAmount = (JsonSchema) properties.get("positiveOrZeroAmount"); + assertEquals(positiveOrZeroAmount.getMinimum(), BigDecimal.ZERO); + assertNull(positiveOrZeroAmount.getExclusiveMinimum()); + + final JsonSchema negativeAmount = (JsonSchema) properties.get("negativeAmount"); + assertEquals(negativeAmount.getExclusiveMaximumValue(), BigDecimal.ZERO); + + final JsonSchema negativeOrZeroAmount = (JsonSchema) properties.get("negativeOrZeroAmount"); + assertEquals(negativeOrZeroAmount.getMaximum(), BigDecimal.ZERO); + assertNull(negativeOrZeroAmount.getExclusiveMaximum()); + + final JsonSchema positiveWithMin = (JsonSchema) properties.get("positiveWithMin"); + assertEquals(positiveWithMin.getMinimum(), new BigDecimal("5")); + assertNull(positiveWithMin.getExclusiveMinimum()); + assertNull(positiveWithMin.getExclusiveMinimumValue()); + + final JsonSchema positiveWithDecimalMin = (JsonSchema) properties.get("positiveWithDecimalMin"); + assertEquals(positiveWithDecimalMin.getMinimum(), new BigDecimal("5.5")); + assertNull(positiveWithMin.getExclusiveMinimum()); + assertNull(positiveWithMin.getExclusiveMinimumValue()); + + final JsonSchema positiveOrZeroWithMin = (JsonSchema) properties.get("positiveOrZeroWithMin"); + assertEquals(positiveOrZeroWithMin.getMinimum(), new BigDecimal("3")); + assertNull(positiveWithMin.getExclusiveMinimum()); + assertNull(positiveWithMin.getExclusiveMinimumValue()); + + final JsonSchema negativeWithMax = (JsonSchema) properties.get("negativeWithMax"); + assertEquals(negativeWithMax.getMaximum(), new BigDecimal("-3")); + assertNull(positiveWithMin.getExclusiveMaximum()); + assertNull(positiveWithMin.getExclusiveMaximumValue()); + + final JsonSchema negativeOrZeroWithMax = (JsonSchema) properties.get("negativeOrZeroWithMax"); + assertEquals(negativeOrZeroWithMax.getMaximum(), new BigDecimal("-2")); + assertNull(positiveWithMin.getExclusiveMaximum()); + assertNull(positiveWithMin.getExclusiveMaximumValue()); + } } 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 82d65ac9db..c7bd09a4d7 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 @@ -841,4 +841,76 @@ public void testApplyNegativeOrZeroConstraintOverridesPositiveMax() { assertEquals(schema.getMaximum(), BigDecimal.ZERO); assertNull(schema.getExclusiveMaximum()); } + + // --- OpenAPI 3.1 tests for @Positive --- + + @Test + public void testApplyPositiveConstraintOnNumberSchemaV31() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema); + + assertTrue(modified); + assertNull(schema.getMinimum()); + assertNull(schema.getExclusiveMinimum()); + assertEquals(schema.getExclusiveMinimumValue(), BigDecimal.ZERO); + } + + @Test + public void testApplyPositiveConstraintV31KeepsStricterValue() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + schema.setExclusiveMinimumValue(new BigDecimal("10")); + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema); + + assertFalse(modified); + assertEquals(schema.getExclusiveMinimumValue(), new BigDecimal("10")); + } + + @Test + public void testApplyPositiveConstraintV31YieldsToDefinedValue() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + schema.setExclusiveMinimumValue(new BigDecimal("-5")); + boolean modified = ValidationAnnotationsUtils.applyPositiveConstraint(schema); + + assertFalse(modified); + assertEquals(schema.getExclusiveMinimumValue(), BigDecimal.valueOf(-5)); + } + + // --- OpenAPI 3.1 tests for @Negative --- + + @Test + public void testApplyNegativeConstraintOnNumberSchemaV31() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema); + + assertTrue(modified); + assertNull(schema.getMaximum()); + assertNull(schema.getExclusiveMaximum()); + assertEquals(schema.getExclusiveMaximumValue(), BigDecimal.ZERO); + } + + @Test + public void testApplyNegativeConstraintV31KeepsStricterValue() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + schema.setExclusiveMaximumValue(new BigDecimal("-5")); + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema); + + assertFalse(modified); + assertEquals(schema.getExclusiveMaximumValue(), BigDecimal.valueOf(-5)); + } + + @Test + public void testApplyNegativeConstraintV31YieldsToDefinedValue() { + Schema schema = new NumberSchema(); + schema.setSpecVersion(io.swagger.v3.oas.models.SpecVersion.V31); + schema.setExclusiveMaximumValue(new BigDecimal("10")); + boolean modified = ValidationAnnotationsUtils.applyNegativeConstraint(schema); + + assertFalse(modified); + assertEquals(schema.getExclusiveMaximumValue(), BigDecimal.TEN); + } }