diff --git a/src/main/java/io/vertx/json/schema/impl/Format.java b/src/main/java/io/vertx/json/schema/impl/Format.java index 179a3782..7e1cb1e4 100644 --- a/src/main/java/io/vertx/json/schema/impl/Format.java +++ b/src/main/java/io/vertx/json/schema/impl/Format.java @@ -182,17 +182,19 @@ private static boolean testUri(String value) { return NOT_URI_FRAGMENT.matcher(value).find() && URI_PATTERN.matcher(value).find(); } - private static final Pattern DURATION_A = Pattern.compile("^-?P\\d+([.,]\\d+)?W$"); - private static final Pattern DURATIION_B = Pattern.compile("^-?P[\\dYMDTHS]*(\\d[.,]\\d+)?[YMDHS]$"); - private static final Pattern DURATIION_C = Pattern.compile("^-?P([.,\\d]+Y)?([.,\\d]+M)?([.,\\d]+D)?(T([.,\\d]+H)?([.,\\d]+M)?([.,\\d]+S)?)?$"); - // Matches to true if a duration string is not a zero duration with a minus sign in front. - private static final Pattern DURATION_D = Pattern.compile("^(-.*[1-9].*|[^-].*)$"); + + private static final Pattern DURATION_A = Pattern.compile("^P\\d+([.,]\\d+)?W$"); + private static final Pattern DURATIION_B = Pattern.compile("^P[\\dYMDTHS]*(\\d[.,]\\d+)?[YMDHS]$"); + private static final Pattern DURATIION_C = Pattern.compile("^P([.,\\d]+Y)?([.,\\d]+M)?([.,\\d]+D)?(T([.,\\d]+H)?([.,\\d]+M)?([.,\\d]+S)?)?$"); + private static final Pattern DURATION_D = Pattern.compile("^-?P(-?\\d+(\\.\\d+)?Y)?(-?\\d+(\\.\\d+)?M)?(-?\\d+(\\.\\d+)?W)?(-?\\d+(\\.\\d+)?D)?(T(-?\\d+(\\.\\d+)?H)?(-?\\d+(\\.\\d+)?M)?(-?\\d+(\\.\\d+)?S)?)?$"); private static boolean testDuration(String value) { return value.length() > 1 && value.length() < 80 && - (DURATION_A.matcher(value).find() || (DURATIION_B.matcher(value).find() && DURATIION_C.matcher(value).find())) && - (DURATION_D.matcher(value).find()); + (DURATION_A.matcher(value).find() || + (DURATIION_B.matcher(value).find() && + DURATIION_C.matcher(value).find())) || + DURATION_D.matcher(value).find(); } private static final Pattern FASTDATETIME = Pattern.compile("^\\d\\d\\d\\d-[0-1]\\d-[0-3]\\d[t\\s](?:[0-2]\\d:[0-5]\\d:[0-5]\\d|23:59:60)(?:\\.\\d+)?(?:z|[+-]\\d\\d(?::?\\d\\d)?)$", Pattern.CASE_INSENSITIVE); diff --git a/src/test/java/io/vertx/json/schema/ValidationTest.java b/src/test/java/io/vertx/json/schema/ValidationTest.java index f2aead54..5da22f1c 100644 --- a/src/test/java/io/vertx/json/schema/ValidationTest.java +++ b/src/test/java/io/vertx/json/schema/ValidationTest.java @@ -9,6 +9,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import java.util.function.Consumer; + @ExtendWith(VertxExtension.class) class ValidationTest { @@ -226,7 +228,7 @@ public void testIssue49b() throws JsonSchemaValidationException { } @Test - void testNegativeDurationValidation() throws JsonSchemaValidationException { + void testDurationValidation() { SchemaRepository repository = SchemaRepository.create(new JsonSchemaOptions().setDraft(Draft.DRAFT201909).setBaseUri("app://")); @@ -246,19 +248,43 @@ void testNegativeDurationValidation() throws JsonSchemaValidationException { "}") )); - JsonObject positiveDuration = new JsonObject().put("duration", "P3W"); - JsonObject negativeDuration = new JsonObject().put("duration", "-P3W"); - JsonObject positiveNullDuration = new JsonObject().put("duration", "P0W"); - JsonObject negativeNullDuration = new JsonObject().put("duration", "-PT0S"); - - repository.validator("acceptable-duration.json").validate(positiveDuration).checkValidity(); - repository.validator("acceptable-duration.json").validate(negativeDuration).checkValidity(); - repository.validator("acceptable-duration.json").validate(positiveNullDuration).checkValidity(); - try { - repository.validator("acceptable-duration.json").validate(negativeNullDuration).checkValidity(); - fail("Should have thrown an exception"); - } catch (JsonSchemaValidationException e) { - // OK - } + Consumer shouldValidate = duration -> { + try { + repository.validator("acceptable-duration.json").validate(new JsonObject().put("duration", duration)).checkValidity(); + } catch (JsonSchemaValidationException ignored) { + fail("Duration " + duration + " should pass validation."); + } + }; + + Consumer shouldNotValidate = duration -> { + try { + repository.validator("acceptable-duration.json").validate(new JsonObject().put("duration", duration)).checkValidity(); + fail("Duration " + duration + " should not pass validation."); + } catch (JsonSchemaValidationException ignored) {} + }; + + shouldValidate.accept("PT0S"); + shouldValidate.accept("-PT0S"); + shouldValidate.accept("PT-0S"); + shouldValidate.accept("P1Y"); + shouldValidate.accept("P1M"); + shouldValidate.accept("P1W"); + shouldValidate.accept("P1D"); + shouldValidate.accept("PT1H"); + shouldValidate.accept("PT1M"); + shouldValidate.accept("PT1S"); + shouldValidate.accept("P1Y2M"); + shouldValidate.accept("P1M2W"); + shouldValidate.accept("P1W2D"); + shouldNotValidate.accept("P1M2Y"); + shouldNotValidate.accept("P1W2M"); + shouldNotValidate.accept("P1D2W"); + shouldValidate.accept("PT1H2M"); + shouldValidate.accept("PT1M2S"); + shouldNotValidate.accept("PT1M2H"); + shouldNotValidate.accept("PT1S2M"); + shouldValidate.accept("P1Y2M3W4DT5H6M7S"); + shouldValidate.accept("P-1Y-2M-3W-4DT-5H-6M-7S"); + shouldValidate.accept("-P-1.0Y2.2M-33.4W-4.2DT5H-7S"); } }