From f376fe6fccd44c82846d212bceec2e66b8f2084a Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Sat, 15 Jun 2024 13:56:15 +0200 Subject: [PATCH] add applyTo to schema and deprecate validator --- .../jsonschema/Applicable.java | 30 ++++++++++++ .../jsonschema/JsonSchema.java | 3 +- .../jsonschema/keyword/Applicator.java | 6 +-- .../jsonschema/FakeJsonSchemaFactory.java | 5 ++ .../jsonschema/JsonSubSchemaTest.java | 5 ++ .../core/DefaultJsonObjectSchema.java | 6 +++ .../jsonschema/core/DefaultJsonSubSchema.java | 18 +++++++ .../jsonschema/core/EmptyJsonSchema.java | 5 ++ .../jsonschema/core/FalseJsonSchema.java | 5 ++ .../jsonschema/core/KeywordPredicate.java | 48 +++++++++++++++++++ .../jsonschema/core/TrueJsonSchema.java | 5 ++ .../AdditionalPropertiesKeyword.java | 2 +- .../vocab/applicator/ContainsKeyword.java | 2 +- .../core/vocab/applicator/ItemsKeyword.java | 4 +- .../core/vocab/applicator/NotKeyword.java | 2 +- .../applicator/PatternPropertiesKeyword.java | 2 +- .../vocab/applicator/PrefixItemsKeyword.java | 2 +- .../core/vocab/applicator/PropertyNames.java | 4 +- .../core/vocab/core/RefKeyword.java | 2 +- .../core/AbstractJsonValueSchemaTest.java | 5 ++ .../jsonschema/core/EmptyJsonSchemaTest.java | 1 + 21 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 api/src/main/java/io/github/sebastiantoepfer/jsonschema/Applicable.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/KeywordPredicate.java diff --git a/api/src/main/java/io/github/sebastiantoepfer/jsonschema/Applicable.java b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/Applicable.java new file mode 100644 index 00000000..ebde4650 --- /dev/null +++ b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/Applicable.java @@ -0,0 +1,30 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema; + +import jakarta.json.JsonValue; + +public interface Applicable { + boolean applyTo(JsonValue instance); +} diff --git a/api/src/main/java/io/github/sebastiantoepfer/jsonschema/JsonSchema.java b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/JsonSchema.java index d332c595..cacd6663 100644 --- a/api/src/main/java/io/github/sebastiantoepfer/jsonschema/JsonSchema.java +++ b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/JsonSchema.java @@ -30,7 +30,8 @@ import java.util.Optional; import java.util.stream.Stream; -public interface JsonSchema extends JsonValue, Printable { +public interface JsonSchema extends JsonValue, Applicable, Printable { + @Deprecated(forRemoval = true) Validator validator(); Optional keywordByName(String name); diff --git a/api/src/main/java/io/github/sebastiantoepfer/jsonschema/keyword/Applicator.java b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/keyword/Applicator.java index 6fefab8c..5eeccba7 100644 --- a/api/src/main/java/io/github/sebastiantoepfer/jsonschema/keyword/Applicator.java +++ b/api/src/main/java/io/github/sebastiantoepfer/jsonschema/keyword/Applicator.java @@ -23,7 +23,7 @@ */ package io.github.sebastiantoepfer.jsonschema.keyword; -import jakarta.json.JsonValue; +import io.github.sebastiantoepfer.jsonschema.Applicable; import java.util.Collection; import java.util.Set; @@ -35,11 +35,9 @@ * * see: http://json-schema.org/draft/2020-12/json-schema-core.html#name-root-schema-and-subschemas- **/ -public interface Applicator extends Keyword { +public interface Applicator extends Keyword, Applicable { @Override default Collection categories() { return Set.of(KeywordCategory.APPLICATOR); } - - boolean applyTo(JsonValue instance); } diff --git a/api/src/test/java/io/github/sebastiantoepfer/jsonschema/FakeJsonSchemaFactory.java b/api/src/test/java/io/github/sebastiantoepfer/jsonschema/FakeJsonSchemaFactory.java index dc0c2931..e5bb9778 100644 --- a/api/src/test/java/io/github/sebastiantoepfer/jsonschema/FakeJsonSchemaFactory.java +++ b/api/src/test/java/io/github/sebastiantoepfer/jsonschema/FakeJsonSchemaFactory.java @@ -45,6 +45,11 @@ public Validator validator() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public boolean applyTo(JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public Optional keywordByName(String name) { throw new UnsupportedOperationException("Not supported yet."); diff --git a/api/src/test/java/io/github/sebastiantoepfer/jsonschema/JsonSubSchemaTest.java b/api/src/test/java/io/github/sebastiantoepfer/jsonschema/JsonSubSchemaTest.java index 2e55ca3d..a59cacae 100644 --- a/api/src/test/java/io/github/sebastiantoepfer/jsonschema/JsonSubSchemaTest.java +++ b/api/src/test/java/io/github/sebastiantoepfer/jsonschema/JsonSubSchemaTest.java @@ -52,6 +52,11 @@ public Validator validator() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public boolean applyTo(JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public Optional keywordByName(final String name) { throw new UnsupportedOperationException("Not supported yet."); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchema.java index 2122b6ef..2b07ccf5 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchema.java @@ -32,6 +32,7 @@ import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.JsonObject; import jakarta.json.JsonPointer; +import jakarta.json.JsonValue; import java.util.Optional; import java.util.stream.Stream; @@ -51,6 +52,11 @@ public Validator validator() { return keywords().collect(collectingAndThen(toList(), KeywordBasedValidator::new)); } + @Override + public boolean applyTo(final JsonValue instance) { + return keywords().map(KeywordPredicate::new).allMatch(prdct -> prdct.test(instance)); + } + @Override public Optional keywordByName(final String name) { return keywords().filter(k -> k.hasName(name)).findFirst(); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java index 639dc4f6..b94ddb92 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java @@ -36,6 +36,7 @@ import jakarta.json.JsonValue; import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Stream; final class DefaultJsonSubSchema implements JsonSubSchema { @@ -58,6 +59,23 @@ public JsonSchema owner() { return owner; } + @Override + public boolean applyTo(final JsonValue instance) { + final Stream> predicates; + if (isJsonObject()) { + predicates = keywords() + .filter( + k -> + k.hasCategory(Keyword.KeywordCategory.ASSERTION) || + k.hasCategory(Keyword.KeywordCategory.APPLICATOR) + ) + .map(KeywordPredicate::new); + } else { + predicates = Stream.of(schema::applyTo); + } + return predicates.allMatch(prdct -> prdct.test(instance)); + } + @Override public Validator validator() { final Validator result; diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchema.java index e53919c6..2990e4ee 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchema.java @@ -39,6 +39,11 @@ public EmptyJsonSchema() { super(JsonValue.EMPTY_JSON_OBJECT); } + @Override + public boolean applyTo(final JsonValue instance) { + return true; + } + @Override public Validator validator() { return new DefaultValidator(new NoCondition<>()); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchema.java index 03bcebc5..c968dfa7 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchema.java @@ -44,6 +44,11 @@ public > T printOn(final T media) { throw new UnsupportedOperationException("false schema not supported yet!"); } + @Override + public boolean applyTo(final JsonValue instance) { + return false; + } + @Override public Validator validator() { return new DefaultValidator(new UnfulfillableCondition<>()); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/KeywordPredicate.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/KeywordPredicate.java new file mode 100644 index 00000000..d3d2a0db --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/KeywordPredicate.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core; + +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.JsonValue; +import java.util.function.Predicate; + +final class KeywordPredicate implements Predicate { + + private final Predicate predicate; + + public KeywordPredicate(final Keyword keyword) { + if (keyword.hasCategory(Keyword.KeywordCategory.ASSERTION)) { + predicate = keyword.asAssertion()::isValidFor; + } else if (keyword.hasCategory(Keyword.KeywordCategory.APPLICATOR)) { + predicate = keyword.asApplicator()::applyTo; + } else { + throw new IllegalArgumentException("keyword must be an assertion or an applicator!"); + } + } + + @Override + public boolean test(final JsonValue t) { + return predicate.test(t); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/TrueJsonSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/TrueJsonSchema.java index 832c4db5..e4cfeeee 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/TrueJsonSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/TrueJsonSchema.java @@ -39,6 +39,11 @@ public TrueJsonSchema() { super(JsonValue.TRUE); } + @Override + public boolean applyTo(final JsonValue instance) { + return true; + } + @Override public Validator validator() { return new DefaultValidator(new NoCondition<>()); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java index 52571818..c219deca 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java @@ -85,7 +85,7 @@ public boolean applyTo(final JsonValue instance) { private boolean additionalPropertiesMatches(final JsonObject instance) { return findPropertiesForValidation(instance) .map(Map.Entry::getValue) - .allMatch(value -> additionalPropertiesSchema.validator().isValid(value)); + .allMatch(additionalPropertiesSchema::applyTo); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java index c22e044b..a7fcd7b6 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java @@ -105,6 +105,6 @@ private JsonValue valueFor(final JsonArray values) { } Stream matchingValues(final JsonArray values) { - return values.stream().filter(contains.validator()::isValid); + return values.stream().filter(contains::applyTo); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java index 0591c7ed..df790058 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java @@ -90,7 +90,7 @@ public JsonValue valueFor(final JsonValue value) { } private boolean appliesToAnyFor(final JsonArray value) { - return itemsForValidation(value).anyMatch(schema.validator()::isValid); + return itemsForValidation(value).anyMatch(schema::applyTo); } @Override @@ -99,7 +99,7 @@ public boolean applyTo(final JsonValue instance) { } private boolean applyTo(final JsonArray items) { - return itemsForValidation(items).allMatch(schema.validator()::isValid); + return itemsForValidation(items).allMatch(schema::applyTo); } private Stream itemsForValidation(final JsonArray items) { diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java index dbda60e0..adb5169b 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java @@ -53,7 +53,7 @@ public NotKeyword(final JsonSchema schema) { @Override public boolean applyTo(final JsonValue instance) { - return !schema.validator().isValid(instance); + return !schema.applyTo(instance); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeyword.java index 8cdb9ed5..64f06a1c 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeyword.java @@ -110,7 +110,7 @@ private boolean propertyMatches(final Map.Entry property) { .stream() .filter(e -> e.getKey().matcher(property.getKey()).find()) .map(Map.Entry::getValue) - .allMatch(schema -> schema.validator().isValid(property.getValue())); + .allMatch(schema -> schema.applyTo(property.getValue())); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeyword.java index 4ce16dae..143b7e26 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeyword.java @@ -94,7 +94,7 @@ public boolean applyTo(final JsonValue instance) { private boolean matchesSchemas(final JsonArray instance) { boolean result = true; for (int i = 0; i < Math.min(schemas.size(), instance.size()); i++) { - result &= schemas.get(i).validator().isValid(instance.get(i)); + result &= schemas.get(i).applyTo(instance.get(i)); if (!result) { break; } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertyNames.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertyNames.java index 8ced4ee2..21e854c7 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertyNames.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertyNames.java @@ -26,7 +26,6 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.InstanceType; import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.Validator; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; import jakarta.json.JsonObject; import jakarta.json.JsonValue; @@ -73,7 +72,6 @@ public boolean applyTo(final JsonValue instance) { } private boolean allProperyNamesMatchesSchema(final JsonObject obj) { - final Validator validator = names.validator(); - return obj.keySet().stream().map(provider::createValue).allMatch(validator::isValid); + return obj.keySet().stream().map(provider::createValue).allMatch(names::applyTo); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java index c31901b0..6f33f0f0 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java @@ -64,7 +64,7 @@ public > T printOn(final T media) { @Override public boolean applyTo(final JsonValue instance) { - return retrieveJsonSchema().validator().isValid(instance); + return retrieveJsonSchema().applyTo(instance); } @Override diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchemaTest.java index 145361cf..7f76e2a5 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchemaTest.java @@ -67,6 +67,11 @@ public MyJsonValueSchema(JsonValue value) { super(value); } + @Override + public boolean applyTo(final JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public Validator validator() { throw new UnsupportedOperationException("Not supported yet."); diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java index 8d106dd8..811e1c76 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java @@ -39,6 +39,7 @@ class EmptyJsonSchemaTest { @ArgumentsSource(JsonValuesArguments.class) void should_be_valid_for_everything(final JsonValue value) { assertThat(new EmptyJsonSchema().validator().isValid(value), is(true)); + assertThat(new EmptyJsonSchema().applyTo(value), is(true)); } @Test