From 7e264dcf9dcf9df1a77d5ca486d0553c463d8362 Mon Sep 17 00:00:00 2001 From: "GitHub Release Action [bot]" Date: Thu, 30 May 2024 16:17:59 +0000 Subject: [PATCH 01/22] bump version to 0.3.0-SNAPSHOT --- api/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- vocabulary-spi/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 65cad91c..96e27abf 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.0 + 0.3.0-SNAPSHOT json-schema-api diff --git a/core/pom.xml b/core/pom.xml index d879113b..b06a3ef3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.0 + 0.3.0-SNAPSHOT json-schema-core diff --git a/pom.xml b/pom.xml index a08617be..79dc52f9 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.0 + 0.3.0-SNAPSHOT Json Schema pom @@ -45,7 +45,7 @@ - 2024-05-30T16:17:44Z + 2024-05-30T16:17:54Z 17 diff --git a/vocabulary-spi/pom.xml b/vocabulary-spi/pom.xml index 32a6838a..47ff506f 100644 --- a/vocabulary-spi/pom.xml +++ b/vocabulary-spi/pom.xml @@ -8,7 +8,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.0 + 0.3.0-SNAPSHOT json-schema-vocabulary-spi From 54dc1148f0651fcb7f2d593da786301a394a6915 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 29 May 2024 22:35:04 +0200 Subject: [PATCH 02/22] add support for allOf keyword --- core/pom.xml | 4 + ...dType.java => SchemaArrayKeywordType.java} | 4 +- .../core/vocab/applicator/AllOfKeyword.java | 70 ++++++++ .../applicator/ApplicatorVocabulary.java | 5 +- .../vocab/applicator/AllOfKeywordTest.java | 150 ++++++++++++++++++ .../applicator/PrefixItemsKeywordTest.java | 4 +- 6 files changed, 231 insertions(+), 6 deletions(-) rename core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/{ArraySubSchemaKeywordType.java => SchemaArrayKeywordType.java} (92%) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index b06a3ef3..3b9d48c8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -117,6 +117,10 @@ **/tests/draft2019-09/additionalItems.json --> **/tests/draft2020-12/additionalProperties.json + **/tests/draft2020-12/boolean_schema.json **/tests/draft2020-12/const.json **/tests/draft2020-12/contains.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java similarity index 92% rename from core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java rename to core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java index 4436ae7f..9e0ff95d 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java @@ -34,12 +34,12 @@ import java.util.Objects; import java.util.function.Function; -public final class ArraySubSchemaKeywordType implements KeywordType { +public final class SchemaArrayKeywordType implements KeywordType { private final String name; private final Function, Keyword> keywordCreator; - public ArraySubSchemaKeywordType(final String name, final Function, Keyword> keywordCreator) { + public SchemaArrayKeywordType(final String name, final Function, Keyword> keywordCreator) { this.name = Objects.requireNonNull(name); this.keywordCreator = Objects.requireNonNull(keywordCreator); } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java new file mode 100644 index 00000000..b78dfe6e --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java @@ -0,0 +1,70 @@ +/* + * 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.vocab.applicator; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * allOf : Array
+ * An instance validates successfully against this keyword if it validates successfully against all schemas defined by + * this keyword’s value.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/allof/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.1 + */ +final class AllOfKeyword implements Applicator { + + static final String NAME = "allOf"; + private final Collection schemas; + + public AllOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).allMatch(v -> v.isValid(instance)); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schemas); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index ba3a0076..c62122df 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -25,8 +25,8 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArraySubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; @@ -48,11 +48,12 @@ public final class ApplicatorVocabulary implements Vocabulary { public ApplicatorVocabulary() { this.vocab = new DefaultVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/applicator"), + new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), - new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), + new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), //normally affeced by minContains and maxContains, but only min has a direct effect! new AffectedByKeywordType( ContainsKeyword.NAME, diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java new file mode 100644 index 00000000..940908cf --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java @@ -0,0 +1,150 @@ +/* + * 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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class AllOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = createKeywordFrom( + Json.createObjectBuilder().add("allOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() + ); + + assertThat(items.hasName("allOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) + ) + ) + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) + ) + ) + ) + .build() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("allOf"), hasItem((hasKey("allOf")))) + ); + } + + @Test + void should_be_valid_if_all_schemas_applies() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) + ) + ) + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) + ) + ) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createValue(25)), + is(true) + ); + } + + @Test + void should_be_invalid_if_any_schemas_not_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) + ) + ) + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) + ) + ) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createValue(10)), + is(false) + ); + } + + private static Keyword createKeywordFrom(final JsonObject json) { + return new SchemaArrayKeywordType("allOf", AllOfKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java index b625575e..a433f374 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java @@ -32,7 +32,7 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArraySubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; @@ -137,7 +137,7 @@ void should_be_printable() { } private static Keyword createKeywordFrom(final JsonObject json) { - return new ArraySubSchemaKeywordType("prefixItems", PrefixItemsKeyword::new).createKeyword( + return new SchemaArrayKeywordType("prefixItems", PrefixItemsKeyword::new).createKeyword( new DefaultJsonSchemaFactory().create(json) ); } From 7cf44d5c40e214dd490d54a48542cf70d7bf2524 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 29 May 2024 22:35:04 +0200 Subject: [PATCH 03/22] add support for anyOf keyword --- core/pom.xml | 1 + .../core/vocab/applicator/AnyOfKeyword.java | 57 +++++++++ .../applicator/ApplicatorVocabulary.java | 1 + .../vocab/applicator/AnyOfKeywordTest.java | 112 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index 3b9d48c8..ad284b22 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -121,6 +121,7 @@ needs anyOf, oneOf :( **/tests/draft2020-12/allOf.json --> + **/tests/draft2020-12/anyOf.json **/tests/draft2020-12/boolean_schema.json **/tests/draft2020-12/const.json **/tests/draft2020-12/contains.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java new file mode 100644 index 00000000..79db69af --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java @@ -0,0 +1,57 @@ +/* + * 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.vocab.applicator; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +final class AnyOfKeyword implements Applicator { + + static final String NAME = "anyOf"; + private final Collection schemas; + + public AnyOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).anyMatch(v -> v.isValid(instance)); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schemas); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index c62122df..58848975 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -49,6 +49,7 @@ public ApplicatorVocabulary() { this.vocab = new DefaultVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/applicator"), new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), + new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java new file mode 100644 index 00000000..7befdafb --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java @@ -0,0 +1,112 @@ +/* + * 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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class AnyOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = createKeywordFrom( + Json.createObjectBuilder().add("anyOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() + ); + + assertThat(items.hasName("anyOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "anyOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) + ) + ) + ) + .build() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("anyOf"), hasItem((hasKey("allOf")))) + ); + } + + @Test + void should_be_valid_if_any_schemas_applies() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "anyOf", + Json.createArrayBuilder().add(JsonValue.FALSE).add(JsonValue.TRUE).add(JsonValue.FALSE) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createValue(25)), + is(true) + ); + } + + @Test + void should_be_invalid_if_no_schemas_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add("anyOf", Json.createArrayBuilder().add(JsonValue.FALSE).add(JsonValue.FALSE)) + .build() + ) + .asApplicator() + .applyTo(Json.createValue(10)), + is(false) + ); + } + + private static Keyword createKeywordFrom(final JsonObject json) { + return new SchemaArrayKeywordType("anyOf", AnyOfKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); + } +} From b17139aa1956cd977f8f77539f29613c7fb6c2e8 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 29 May 2024 22:35:04 +0200 Subject: [PATCH 04/22] add support for oneOf keyword --- core/pom.xml | 1 + .../applicator/ApplicatorVocabulary.java | 1 + .../core/vocab/applicator/OneOfKeyword.java | 70 +++++++ .../vocab/applicator/OneOfKeywordTest.java | 197 ++++++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index ad284b22..8e7b5d69 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -122,6 +122,7 @@ **/tests/draft2020-12/allOf.json --> **/tests/draft2020-12/anyOf.json + **/tests/draft2020-12/oneOf.json **/tests/draft2020-12/boolean_schema.json **/tests/draft2020-12/const.json **/tests/draft2020-12/contains.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 58848975..1dc844f2 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -50,6 +50,7 @@ public ApplicatorVocabulary() { URI.create("https://json-schema.org/draft/2020-12/vocab/applicator"), new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), + new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.java new file mode 100644 index 00000000..a3054172 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.java @@ -0,0 +1,70 @@ +/* + * 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.vocab.applicator; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * oneOf : Array
+ * An instance validates successfully against this keyword if it validates successfully against exactly one schema + * defined by this keyword’s value.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/oneof/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.3 + */ +final class OneOfKeyword implements Applicator { + + static final String NAME = "oneOf"; + private final Collection schemas; + + public OneOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).filter(v -> v.isValid(instance)).limit(2).count() == 1; + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schemas); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java new file mode 100644 index 00000000..94315583 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java @@ -0,0 +1,197 @@ +/* + * 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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class OneOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = createKeywordFrom( + Json.createObjectBuilder().add("oneOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() + ); + + assertThat(items.hasName("oneOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "oneOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + ) + .build() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("oneOf"), hasItem((hasKey("properties")))) + ); + } + + @Test + void should_be_valid_if_exactly_one_schema_applies() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "oneOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_none_schema_not_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "oneOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), + is(false) + ); + } + + @Test + void should_be_invalid_if_more_than_one_schema_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "oneOf", + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder() + .add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + ) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").add("bar", 33).build()), + is(false) + ); + } + + private static Keyword createKeywordFrom(final JsonObject json) { + return new SchemaArrayKeywordType("oneOf", OneOfKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); + } +} From ae028f25f745993c419acaf4da63a2b9d41b6e56 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:43:24 +0200 Subject: [PATCH 05/22] enable test for allOf -> needed oneOf is ready --- core/pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 8e7b5d69..e5af61a6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -117,10 +117,7 @@ **/tests/draft2019-09/additionalItems.json --> **/tests/draft2020-12/additionalProperties.json - **/tests/draft2020-12/anyOf.json **/tests/draft2020-12/oneOf.json **/tests/draft2020-12/boolean_schema.json From a37f02b6abcf86b01937a2d421412b8836d38953 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:16:35 +0200 Subject: [PATCH 06/22] introduce a more flexible way to define affectedBy relationships of keyword instead of define a affectedBy relationship with list of strings we us a list of new introduces object AffectedBy with a name (the old string) and a new AffectByType. The AffectByType can be Extends this only shows that the keyword stays not alone and the validation result can be altered. Or Replace with how the names say replace the affected keyword with the affectedBy keyword. this allow to change the validation result in both directions und a more flexible way to do it. --- core/pom.xml | 20 +- .../core/keyword/type/AffectByType.java | 43 +++++ .../core/keyword/type/AffectedBy.java | 89 +++++++++ .../keyword/type/AffectedByKeywordType.java | 40 ++-- .../core/keyword/type/ReplacingKeyword.java | 114 ++++++++++++ .../applicator/ApplicatorVocabulary.java | 12 +- .../vocab/applicator/ContainsKeyword.java | 7 +- .../core/keyword/type/AffectedByTest.java | 60 ++++++ .../core/keyword/type/MockKeyword.java | 80 ++++++++ .../keyword/type/ReplacingKeywordTest.java | 176 ++++++++++++++++++ .../vocab/applicator/ContainsKeywordTest.java | 31 +-- 11 files changed, 609 insertions(+), 63 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index e5af61a6..833426d5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -81,14 +81,7 @@ com.google.errorprone error_prone_annotations test - - - - jakarta.json - jakarta.json-api - provided - - + org.eclipse.parsson parsson @@ -99,6 +92,17 @@ media-core test + + nl.jqno.equalsverifier + equalsverifier + test + + + + jakarta.json + jakarta.json-api + provided + com.github.spotbugs diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java new file mode 100644 index 00000000..de1ef5d6 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java @@ -0,0 +1,43 @@ +/* + * 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.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; + +public enum AffectByType { + EXTENDS { + @Override + Keyword affect(final Keyword affectedKeyword) { + return affectedKeyword; + } + }, + REPLACE { + @Override + Keyword affect(final Keyword affectedKeyword) { + return new ReplacingKeyword(affectedKeyword); + } + }; + + abstract Keyword affect(final Keyword affectedKeyword); +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java new file mode 100644 index 00000000..0418be5c --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java @@ -0,0 +1,89 @@ +/* + * 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.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +public final class AffectedBy implements Comparable { + + private final AffectByType type; + private final String name; + + public AffectedBy(final AffectByType type, final String name) { + this.type = Objects.requireNonNull(type); + this.name = Objects.requireNonNull(name); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(this.type); + hash = 83 * hash + Objects.hashCode(this.name); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return compareTo((AffectedBy) obj) == 0; + } + + @Override + public int compareTo(final AffectedBy other) { + final int result; + if (type.compareTo(other.type) == 0) { + result = name.compareTo(other.name); + } else { + result = type.compareTo(other.type); + } + return result; + } + + Function findAffectedByKeywordIn(final JsonSchema schema) { + final UnaryOperator result; + if (schema.keywordByName(name).isPresent()) { + result = type::affect; + } else { + result = k -> k; + } + return result; + } + + @Override + public String toString() { + return "AffectedBy{" + "type=" + type + ", name=" + name + '}'; + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java index ab528879..8a564151 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java @@ -23,27 +23,26 @@ */ package io.github.sebastiantoepfer.jsonschema.core.keyword.type; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; - import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Function; public class AffectedByKeywordType implements KeywordType { private final String name; - private final List affectedBy; - private final BiFunction, JsonSchema, Keyword> keywordCreator; + private final Collection affectedBy; + private final Function keywordCreator; public AffectedByKeywordType( final String name, - final List affectedBy, - final BiFunction, JsonSchema, Keyword> keywordCreator + final Collection affectedBy, + final Function keywordCreator ) { this.name = Objects.requireNonNull(name); this.affectedBy = List.copyOf(affectedBy); @@ -57,24 +56,24 @@ public String name() { @Override public Keyword createKeyword(final JsonSchema schema) { - return new AffectedByKeyword(schema, name, affectedBy, keywordCreator); + return new AffectedKeyword(schema, name, affectedBy, keywordCreator); } - static final class AffectedByKeyword extends KeywordRelationship { + static final class AffectedKeyword extends KeywordRelationship { private final JsonSchema schema; - private final List affectedBy; - private final BiFunction, JsonSchema, Keyword> keywordCreator; + private final SortedSet affectedBy; + private final Function keywordCreator; - public AffectedByKeyword( + public AffectedKeyword( final JsonSchema schema, final String name, - final List affectedBy, - final BiFunction, JsonSchema, Keyword> keywordCreator + final Collection affectedBy, + final Function keywordCreator ) { super(name); this.schema = Objects.requireNonNull(schema); - this.affectedBy = List.copyOf(affectedBy); + this.affectedBy = new TreeSet<>(affectedBy); this.keywordCreator = Objects.requireNonNull(keywordCreator); } @@ -82,9 +81,10 @@ public AffectedByKeyword( protected Keyword delegate() { return affectedBy .stream() - .map(schema::keywordByName) - .flatMap(Optional::stream) - .collect(collectingAndThen(toList(), k -> keywordCreator.apply(k, schema))); + .map(a -> a.findAffectedByKeywordIn(schema)) + .reduce(Function::andThen) + .orElseThrow() + .apply(keywordCreator.apply(schema)); } } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java new file mode 100644 index 00000000..8e9b59fe --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java @@ -0,0 +1,114 @@ +/* + * 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.keyword.type; + +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toSet; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Assertion; +import io.github.sebastiantoepfer.jsonschema.keyword.Identifier; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.ReservedLocation; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; + +final class ReplacingKeyword implements Keyword { + + private final Keyword affectedKeyword; + private final Collection categoriesToReplace; + + public ReplacingKeyword(final Keyword affectedKeyword) { + this(affectedKeyword, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); + } + + public ReplacingKeyword(final Keyword affectedKeyword, final Collection categoriesToReplace) { + this.affectedKeyword = Objects.requireNonNull(affectedKeyword); + this.categoriesToReplace = List.copyOf(categoriesToReplace); + } + + @Override + public Identifier asIdentifier() { + if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asIdentifier(); + } + } + + @Override + public Assertion asAssertion() { + if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asAssertion(); + } + } + + @Override + public Annotation asAnnotation() { + if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asAnnotation(); + } + } + + @Override + public Applicator asApplicator() { + if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asApplicator(); + } + } + + @Override + public ReservedLocation asReservedLocation() { + if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asReservedLocation(); + } + } + + @Override + public Collection categories() { + return affectedKeyword.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); + } + + @Override + public boolean hasName(final String string) { + return affectedKeyword.hasName(string); + } + + @Override + public > T printOn(final T media) { + return affectedKeyword.printOn(media); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 1dc844f2..e9cd01e7 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -24,6 +24,8 @@ package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator; import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectByType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedBy; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; @@ -56,12 +58,14 @@ public ApplicatorVocabulary() { new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - //normally affeced by minContains and maxContains, but only min has a direct effect! + new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, - List.of("minContains"), - (a, schema) -> - new SubSchemaKeywordType(ContainsKeyword.NAME, s -> new ContainsKeyword(a, s)).createKeyword(schema) + List.of( + new AffectedBy(AffectByType.REPLACE, "minContains"), + new AffectedBy(AffectByType.EXTENDS, "maxContains") + ), + new SubSchemaKeywordType(ContainsKeyword.NAME, ContainsKeyword::new)::createKeyword ) ); } 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 49636d27..c22e044b 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 @@ -30,7 +30,6 @@ import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.util.Collection; @@ -54,10 +53,8 @@ final class ContainsKeyword implements Applicator, Annotation { static final String NAME = "contains"; private final JsonSchema contains; - private final List affectedBy; - public ContainsKeyword(final List affectedBy, final JsonSchema contains) { - this.affectedBy = List.copyOf(affectedBy); + public ContainsKeyword(final JsonSchema contains) { this.contains = Objects.requireNonNull(contains); } @@ -82,7 +79,7 @@ public boolean applyTo(final JsonValue instance) { } private boolean contains(final JsonArray array) { - return !affectedBy.isEmpty() || matchingValues(array).findAny().isPresent(); + return matchingValues(array).findAny().isPresent(); } @Override diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java new file mode 100644 index 00000000..bce5b3ab --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java @@ -0,0 +1,60 @@ +/* + * 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.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +import java.util.List; +import java.util.TreeSet; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class AffectedByTest { + + @Test + void should_fullfil_equals_contract() { + EqualsVerifier.forClass(AffectedBy.class).withNonnullFields("type", "name").verify(); + } + + @Test + void should_be_sorted_correctly() { + assertThat( + new TreeSet<>( + List.of( + new AffectedBy(AffectByType.REPLACE, "d"), + new AffectedBy(AffectByType.EXTENDS, "c"), + new AffectedBy(AffectByType.EXTENDS, "b"), + new AffectedBy(AffectByType.REPLACE, "a") + ) + ), + contains( + new AffectedBy(AffectByType.EXTENDS, "b"), + new AffectedBy(AffectByType.EXTENDS, "c"), + new AffectedBy(AffectByType.REPLACE, "a"), + new AffectedBy(AffectByType.REPLACE, "d") + ) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java new file mode 100644 index 00000000..35ba659b --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java @@ -0,0 +1,80 @@ +/* + * 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.keyword.type; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Assertion; +import io.github.sebastiantoepfer.jsonschema.keyword.Identifier; +import io.github.sebastiantoepfer.jsonschema.keyword.ReservedLocation; +import jakarta.json.JsonValue; +import java.net.URI; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +final class MockKeyword implements Identifier, Assertion, Annotation, Applicator, ReservedLocation { + + private final String name; + + public MockKeyword(final String name) { + this.name = name; + } + + @Override + public URI asUri() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isValidFor(final JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public JsonValue valueFor(final JsonValue value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean applyTo(final JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection categories() { + return EnumSet.allOf(KeywordCategory.class); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(this.name, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(name, name); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java new file mode 100644 index 00000000..0f7c1a87 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java @@ -0,0 +1,176 @@ +/* + * 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.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword.KeywordCategory; +import java.util.EnumSet; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class ReplacingKeywordTest { + + @Test + void should_not_be_createable_as_identifier_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.IDENTIFIER) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asIdentifier()); + } + + @Test + void should_return_identifier_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.IDENTIFIER)) + ).asIdentifier(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_assertion_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.ASSERTION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asAssertion()); + } + + @Test + void should_return_assertion_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.ASSERTION)) + ).asAssertion(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_annotation_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.ANNOTATION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asAnnotation()); + } + + @Test + void should_return_annotation_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.ANNOTATION)) + ).asAnnotation(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_applicator_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.APPLICATOR) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asApplicator()); + } + + @Test + void should_return_applicator_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.APPLICATOR)) + ).asApplicator(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_reserved_location_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.RESERVED_LOCATION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asReservedLocation()); + } + + @Test + void should_return_reserved_location_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.RESERVED_LOCATION)) + ).asReservedLocation(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_name_of_decoded_keyword() { + final Keyword keyword = new ReplacingKeyword(new MockKeyword("test")); + + assertThat(keyword.hasName("test"), is(true)); + assertThat(keyword.hasName("bunny"), is(false)); + } + + @Test + void should_print_as_decored_keyword() { + assertThat( + new ReplacingKeyword(new MockKeyword("test")).printOn(new HashMapMedia()), + Matchers.hasEntry("test", "test") + ); + } + + @ParameterizedTest + @EnumSource(KeywordCategory.class) + void should_not_return_replaced_keyword_category(final Keyword.KeywordCategory category) { + assertThat( + new ReplacingKeyword(new MockKeyword("test"), EnumSet.of(category)).categories(), + both(not(hasItem(category))).and((Matcher) is(not(empty()))) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java index e6ddbad0..02c8c5f8 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java @@ -30,14 +30,13 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import java.util.List; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -56,10 +55,7 @@ void should_be_printable() { @Test void should_know_his_name() { - final Keyword enumKeyword = new ContainsKeyword( - List.of(), - new DefaultJsonSchemaFactory().create(JsonValue.TRUE) - ); + final Keyword enumKeyword = new ContainsKeyword(JsonSchemas.load(JsonValue.TRUE)); assertThat(enumKeyword.hasName("contains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -87,21 +83,6 @@ void should_apply_for_non_array() { ); } - @Test - void should_apply_to_empty_array_if_min_andor_max_provided() { - assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "number")) - .add("minContains", 0) - .build() - ) - .asApplicator() - .applyTo(JsonValue.EMPTY_JSON_ARRAY), - is(true) - ); - } - @Test void should_not_apply_to_empty_array_if_non_min_andor_max_is_provided() { assertThat( @@ -232,10 +213,8 @@ void should_return_true_if_all_item_applies() { } private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectedByKeywordType( - "contains", - List.of("minContains", "maxContains"), - (a, schema) -> new SubSchemaKeywordType("contains", s -> new ContainsKeyword(a, s)).createKeyword(schema) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); + return new SubSchemaKeywordType("contains", ContainsKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); } } From fa76f021b6d67a8ef8d03bebf6975c5adb7b7d24 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:59:40 +0200 Subject: [PATCH 07/22] introduce a more flexible way to define affects relationships of keyword instead of define a affects relationship with list of strings we us a list of new introduces object Affects with a name (the old string) and a new AbsenceStrategy. The AbsenceStrategy can be use to define what should be happens if the keyword is missing. At default is exists two: Provide a default value -> useful if the affected works without the affectedBy one Replace -> useful if the affected doesn't works without the affectedBy one --- .../keyword/type/AffectedByKeywordType.java | 5 +- .../jsonschema/core/keyword/type/Affects.java | 94 ++++++++++++++++++ .../core/keyword/type/AffectsKeywordType.java | 55 +++++++---- .../core/keyword/type/ReplacingKeyword.java | 26 ++--- .../applicator/ApplicatorVocabulary.java | 2 +- .../vocab/validation/MaxContainsKeyword.java | 33 ++++--- .../vocab/validation/MinContainsKeyword.java | 33 ++++--- .../vocab/validation/NumberOfMatches.java | 43 ++++++++ .../validation/ValidationVocabulary.java | 12 ++- .../core/keyword/type/AffectsTest.java | 99 +++++++++++++++++++ .../validation/MaxContainsKeywordTest.java | 26 ++--- .../validation/MinContainsKeywordTest.java | 26 ++--- 12 files changed, 357 insertions(+), 97 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java index 8a564151..4f16fd38 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java @@ -33,7 +33,7 @@ import java.util.TreeSet; import java.util.function.Function; -public class AffectedByKeywordType implements KeywordType { +public final class AffectedByKeywordType implements KeywordType { private final String name; private final Collection affectedBy; @@ -44,6 +44,9 @@ public AffectedByKeywordType( final Collection affectedBy, final Function keywordCreator ) { + if (affectedBy.isEmpty()) { + throw new IllegalArgumentException("affectedBy can not be empty!"); + } this.name = Objects.requireNonNull(name); this.affectedBy = List.copyOf(affectedBy); this.keywordCreator = Objects.requireNonNull(keywordCreator); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java new file mode 100644 index 00000000..62c181d3 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java @@ -0,0 +1,94 @@ +/* + * 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.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; +import jakarta.json.JsonValue; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +public final class Affects { + + private final String name; + private final AbsenceStrategy strategy; + + public Affects(final String name, final JsonValue answerInAbsence) { + this(name, new ProvideDefaultValue(answerInAbsence)); + } + + public Affects(final String name, final AbsenceStrategy strategy) { + this.name = Objects.requireNonNull(name); + this.strategy = Objects.requireNonNull(strategy); + } + + @Override + public String toString() { + return "Affects{" + "name=" + name + ", strategy=" + strategy.getClass() + '}'; + } + + Map.Entry> findAffectsKeywordIn(final JsonSchema schema) { + final Map.Entry> result; + final Optional annotation = schema + .keywordByName(name) + .filter(k -> k.hasCategory(Keyword.KeywordCategory.ANNOTATION)) + .map(Keyword::asAnnotation); + if (annotation.isPresent()) { + result = Map.entry(annotation.get(), k -> k); + } else { + result = strategy.create(name); + } + return result; + } + + public interface AbsenceStrategy { + Map.Entry> create(String name); + } + + public static final class ReplaceKeyword implements AbsenceStrategy { + + @Override + public Map.Entry> create(final String name) { + return Map.entry(new StaticAnnotation(name, JsonValue.NULL), ReplacingKeyword::new); + } + } + + public static final class ProvideDefaultValue implements AbsenceStrategy { + + private final JsonValue answerInAbsence; + + public ProvideDefaultValue(final JsonValue answerInAbsence) { + this.answerInAbsence = Objects.requireNonNullElse(answerInAbsence, JsonValue.NULL); + } + + @Override + public Map.Entry> create(final String name) { + return Map.entry(new StaticAnnotation(name, answerInAbsence), k -> k); + } + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java index 33a27e87..5cf8620d 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java @@ -27,24 +27,27 @@ import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; -import jakarta.json.JsonValue; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; +import java.util.function.Function; public class AffectsKeywordType implements KeywordType { private final String name; - private final String affects; - private final BiFunction keywordCreator; + private final Collection affects; + private final BiFunction, JsonSchema, Keyword> keywordCreator; public AffectsKeywordType( final String name, - final String affects, - final BiFunction keywordCreator + final Collection affects, + final BiFunction, JsonSchema, Keyword> keywordCreator ) { - this.name = name; - this.affects = affects; + this.name = Objects.requireNonNull(name); + this.affects = List.copyOf(affects); this.keywordCreator = keywordCreator; } @@ -55,36 +58,46 @@ public String name() { @Override public Keyword createKeyword(final JsonSchema schema) { - return new AffectsKeyword(schema, name, affects, keywordCreator); + return new AffectsKeyword(schema, name, List.copyOf(affects), keywordCreator); } static final class AffectsKeyword extends KeywordRelationship { private final JsonSchema schema; - private final String affects; - private final BiFunction keywordCreator; + private final Collection affects; + private final BiFunction, JsonSchema, Keyword> keywordCreator; public AffectsKeyword( final JsonSchema schema, final String name, - final String affects, - final BiFunction keywordCreator + final List affects, + final BiFunction, JsonSchema, Keyword> keywordCreator ) { super(name); this.schema = Objects.requireNonNull(schema); - this.affects = affects; + this.affects = List.copyOf(affects); this.keywordCreator = Objects.requireNonNull(keywordCreator); } @Override protected Keyword delegate() { - return keywordCreator.apply( - schema - .keywordByName(affects) - .map(Keyword::asAnnotation) - .orElseGet(() -> new StaticAnnotation(affects, JsonValue.NULL)), - schema - ); + //ugly ... map.entry is not the right structure ... + final Map.Entry, Function> p = affects + .stream() + .map(a -> a.findAffectsKeywordIn(schema)) + .reduce( + Map.entry(List.of(), k -> k), + ( + Map.Entry, Function> t, + Map.Entry> u + ) -> { + final ArrayList newAnnotations = new ArrayList<>(t.getKey()); + newAnnotations.add(u.getKey()); + return Map.entry(newAnnotations, t.getValue().andThen(u.getValue())); + }, + (l, r) -> null + ); + return p.getValue().apply(keywordCreator.apply(p.getKey(), schema)); } } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java index 8e9b59fe..39d5a619 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java @@ -40,15 +40,15 @@ final class ReplacingKeyword implements Keyword { - private final Keyword affectedKeyword; + private final Keyword keywordToReplace; private final Collection categoriesToReplace; - public ReplacingKeyword(final Keyword affectedKeyword) { - this(affectedKeyword, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); + public ReplacingKeyword(final Keyword keywordToReplace) { + this(keywordToReplace, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); } - public ReplacingKeyword(final Keyword affectedKeyword, final Collection categoriesToReplace) { - this.affectedKeyword = Objects.requireNonNull(affectedKeyword); + public ReplacingKeyword(final Keyword keywordToReplace, final Collection categoriesToReplace) { + this.keywordToReplace = Objects.requireNonNull(keywordToReplace); this.categoriesToReplace = List.copyOf(categoriesToReplace); } @@ -57,7 +57,7 @@ public Identifier asIdentifier() { if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asIdentifier(); + return keywordToReplace.asIdentifier(); } } @@ -66,7 +66,7 @@ public Assertion asAssertion() { if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asAssertion(); + return keywordToReplace.asAssertion(); } } @@ -75,7 +75,7 @@ public Annotation asAnnotation() { if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asAnnotation(); + return keywordToReplace.asAnnotation(); } } @@ -84,7 +84,7 @@ public Applicator asApplicator() { if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asApplicator(); + return keywordToReplace.asApplicator(); } } @@ -93,22 +93,22 @@ public ReservedLocation asReservedLocation() { if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asReservedLocation(); + return keywordToReplace.asReservedLocation(); } } @Override public Collection categories() { - return affectedKeyword.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); + return keywordToReplace.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); } @Override public boolean hasName(final String string) { - return affectedKeyword.hasName(string); + return keywordToReplace.hasName(string); } @Override public > T printOn(final T media) { - return affectedKeyword.printOn(media); + return keywordToReplace.printOn(media); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index e9cd01e7..d3bd39fc 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -58,7 +58,7 @@ public ApplicatorVocabulary() { new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), + new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, List.of( diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java index e9e51768..94d9fada 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java @@ -30,6 +30,8 @@ import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.math.BigInteger; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -47,11 +49,11 @@ final class MaxContainsKeyword implements Assertion { static final String NAME = "maxContains"; - private final Annotation affects; + private final Collection affects; private final BigInteger maxContains; - public MaxContainsKeyword(final Annotation affects, final BigInteger maxContains) { - this.affects = Objects.requireNonNull(affects); + public MaxContainsKeyword(final Collection affects, final BigInteger maxContains) { + this.affects = List.copyOf(affects); this.maxContains = Objects.requireNonNull(maxContains); } @@ -67,20 +69,21 @@ public > T printOn(final T media) { @Override public boolean isValidFor(final JsonValue instance) { - return ( - !InstanceType.ARRAY.isInstance(instance) || isValidFor(affects.valueFor(instance), instance.asJsonArray()) + return (!InstanceType.ARRAY.isInstance(instance) || isValidFor(instance.asJsonArray())); + } + + private boolean isValidFor(final JsonArray instance) { + return isValidFor( + affects + .stream() + .map(a -> a.valueFor(instance)) + .map(v -> new NumberOfMatches(instance, v)) + .mapToInt(NumberOfMatches::count) + .sum() ); } - private boolean isValidFor(final JsonValue containing, final JsonArray values) { - final boolean result; - if (JsonValue.NULL.equals(containing)) { - result = true; - } else if (JsonValue.TRUE.equals(containing)) { - result = values.size() <= maxContains.intValue(); - } else { - result = containing.asJsonArray().size() <= maxContains.intValue(); - } - return result; + private boolean isValidFor(final int containing) { + return containing <= maxContains.intValue(); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java index 4b6ef50b..e8277014 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java @@ -30,6 +30,8 @@ import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.math.BigInteger; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -47,11 +49,11 @@ final class MinContainsKeyword implements Assertion { static final String NAME = "minContains"; - private final Annotation affects; + private final Collection affects; private final BigInteger minContains; - public MinContainsKeyword(final Annotation affects, final BigInteger minContains) { - this.affects = Objects.requireNonNull(affects); + public MinContainsKeyword(final Collection affects, final BigInteger minContains) { + this.affects = List.copyOf(affects); this.minContains = Objects.requireNonNull(minContains); } @@ -67,20 +69,21 @@ public > T printOn(final T media) { @Override public boolean isValidFor(final JsonValue instance) { - return ( - !InstanceType.ARRAY.isInstance(instance) || isValidFor(affects.valueFor(instance), instance.asJsonArray()) + return (!InstanceType.ARRAY.isInstance(instance) || isValidFor(instance.asJsonArray())); + } + + private boolean isValidFor(final JsonArray instance) { + return isValidFor( + affects + .stream() + .map(a -> a.valueFor(instance)) + .map(v -> new NumberOfMatches(instance, v)) + .mapToInt(NumberOfMatches::count) + .sum() ); } - private boolean isValidFor(final JsonValue containing, final JsonArray values) { - final boolean result; - if (JsonValue.NULL.equals(containing)) { - result = true; - } else if (JsonValue.TRUE.equals(containing)) { - result = values.size() >= minContains.intValue(); - } else { - result = containing.asJsonArray().size() >= minContains.intValue(); - } - return result; + private boolean isValidFor(final int containing) { + return containing >= minContains.intValue(); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java new file mode 100644 index 00000000..bb8f07da --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java @@ -0,0 +1,43 @@ +/* + * 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.vocab.validation; + +import jakarta.json.JsonArray; +import jakarta.json.JsonValue; +import java.util.Objects; + +final class NumberOfMatches { + + private final JsonArray array; + private final JsonValue value; + + public NumberOfMatches(final JsonArray array, final JsonValue value) { + this.array = Objects.requireNonNull(array); + this.value = Objects.requireNonNull(value); + } + + public int count() { + return value == JsonValue.TRUE ? array.size() : value.asJsonArray().size(); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java index abc376e7..86e69b80 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java @@ -24,6 +24,7 @@ package io.github.sebastiantoepfer.jsonschema.core.vocab.validation; import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AnyKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArrayKeywordType; @@ -37,6 +38,7 @@ import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; +import java.util.List; import java.util.Optional; public final class ValidationVocabulary implements Vocabulary { @@ -65,7 +67,7 @@ public ValidationVocabulary(final JsonProvider jsonContext) { new IntegerKeywordType(jsonContext, MinItemsKeyword.NAME, MinItemsKeyword::new), new AffectsKeywordType( MaxContainsKeyword.NAME, - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (affects, schema) -> new IntegerKeywordType( JsonProvider.provider(), @@ -75,13 +77,13 @@ public ValidationVocabulary(final JsonProvider jsonContext) { ), new AffectsKeywordType( MinContainsKeyword.NAME, - "contains", - (a, s) -> + List.of(new Affects("contains", new Affects.ReplaceKeyword())), + (affects, schema) -> new IntegerKeywordType( JsonProvider.provider(), MinContainsKeyword.NAME, - value -> new MinContainsKeyword(a, value) - ).createKeyword(s) + value -> new MinContainsKeyword(affects, value) + ).createKeyword(schema) ), new BooleanKeywordType(jsonContext, UniqueItemsKeyword.NAME, UniqueItemsKeyword::new) ); diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java new file mode 100644 index 00000000..5e13cce3 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java @@ -0,0 +1,99 @@ +/* + * 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.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.junit.jupiter.api.Test; + +class AffectsTest { + + @Test + void should_handle_non_annotation_as_absence() { + assertThat( + new Affects("type", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) + .getKey() + .valueFor(JsonValue.FALSE), + is(JsonValue.EMPTY_JSON_OBJECT) + ); + } + + @Test + void should_return_original_annotation() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("test", Json.createObjectBuilder().add("type", "number")) + ) + .build() + ) + ) + .getKey() + .valueFor(Json.createObjectBuilder().add("test", 1L).build()), + is(Json.createArrayBuilder().add("test").build()) + ); + } + + @Test + void should_create_original_annotation() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("test", Json.createObjectBuilder().add("type", "number")) + ) + .build() + ) + ) + .getValue() + .apply(new MockKeyword("test")) + .hasName("test"), + is(true) + ); + } + + @Test + void should_create_original_annotation_also_for_missing() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn(JsonSchemas.load(JsonValue.TRUE)) + .getValue() + .apply(new MockKeyword("test")) + .hasName("test"), + is(true) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java index 2b7a3861..bff8d9b2 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java @@ -29,6 +29,7 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; @@ -38,6 +39,7 @@ import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import java.math.BigInteger; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -45,7 +47,10 @@ class MaxContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MaxContainsKeyword(new StaticAnnotation("", JsonValue.NULL), BigInteger.ONE); + final Keyword enumKeyword = new MaxContainsKeyword( + List.of(new StaticAnnotation("", JsonValue.NULL)), + BigInteger.ONE + ); assertThat(enumKeyword.hasName("maxContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); } @@ -61,23 +66,18 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()) + createKeywordFrom( + Json.createObjectBuilder() + .add("contains", Json.createObjectBuilder().add("type", "string")) + .add("maxContains", 2) + .build() + ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } - @Test - void should_be_valid_if_no_contains_is_present() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("foo").build()), - is(true) - ); - } - @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( @@ -186,7 +186,7 @@ void should_be_invalid_if_contains_applies_to_all_and_more_items_in_array() { private static Keyword createKeywordFrom(final JsonObject json) { return new AffectsKeywordType( "maxContains", - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (a, s) -> new IntegerKeywordType( JsonProvider.provider(), diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java index 682bcab6..645feeb1 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java @@ -29,6 +29,7 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; @@ -38,6 +39,7 @@ import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import java.math.BigInteger; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -45,7 +47,10 @@ class MinContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MinContainsKeyword(new StaticAnnotation("", JsonValue.NULL), BigInteger.ONE); + final Keyword enumKeyword = new MinContainsKeyword( + List.of(new StaticAnnotation("", JsonValue.NULL)), + BigInteger.ONE + ); assertThat(enumKeyword.hasName("minContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -62,23 +67,18 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()) + createKeywordFrom( + Json.createObjectBuilder() + .add("contains", Json.createObjectBuilder().add("type", "string")) + .add("minContains", 2) + .build() + ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } - @Test - void should_be_valid_if_no_contains_is_present() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("foo").build()), - is(true) - ); - } - @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( @@ -187,7 +187,7 @@ void should_be_invalid_if_contains_applies_to_all_and_less_items_in_array() { private static Keyword createKeywordFrom(final JsonObject json) { return new AffectsKeywordType( "minContains", - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (a, s) -> new IntegerKeywordType( JsonProvider.provider(), From 2a09ac7a403baee3c5d72cdbeb4360f1494e7e40 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 29 May 2024 22:35:04 +0200 Subject: [PATCH 08/22] add support for not keyword --- core/pom.xml | 3 + .../applicator/ApplicatorVocabulary.java | 1 + .../core/vocab/applicator/NotKeyword.java | 68 +++++++++++++ .../core/vocab/applicator/NotKeywordTest.java | 96 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index 833426d5..713b9a52 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -148,6 +148,9 @@ **/tests/draft2020-12/minLength.json **/tests/draft2020-12/minimum.json **/tests/draft2020-12/minProperties.json + **/tests/draft2020-12/multipleOf.json **/tests/draft2020-12/pattern.json **/tests/draft2020-12/patternProperties.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index d3bd39fc..7d71bcf7 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -53,6 +53,7 @@ public ApplicatorVocabulary() { new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new), + new SubSchemaKeywordType(NotKeyword.NAME, NotKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), 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 new file mode 100644 index 00000000..dbda60e0 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java @@ -0,0 +1,68 @@ +/* + * 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.vocab.applicator; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonValue; +import java.util.Objects; + +/** + * not : Schema
+ * An instance is valid against this keyword if it fails to validate successfully against the schema defined by + * this keyword.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/not/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.4 + */ +final class NotKeyword implements Applicator { + + static final String NAME = "not"; + private final JsonSchema schema; + + public NotKeyword(final JsonSchema schema) { + this.schema = Objects.requireNonNull(schema); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return !schema.validator().isValid(instance); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schema); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java new file mode 100644 index 00000000..f03d810d --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java @@ -0,0 +1,96 @@ +/* + * 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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class NotKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.FALSE).build()); + + assertThat(items.hasName("not"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add( + "not", + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .build() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("not"), hasKey("properties")) + ); + } + + @Test + void should_be_valid_if_schema_not_apply() { + assertThat( + createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.FALSE).build()) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_schema_apply() { + assertThat( + createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.TRUE).build()) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), + is(false) + ); + } + + private static Keyword createKeywordFrom(final JsonObject json) { + return new SubSchemaKeywordType("not", NotKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); + } +} From 61d2021eec9cd80f7311b0d040bc55a8053b99ea Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:34:17 +0200 Subject: [PATCH 09/22] add toString to all schemas --- .../jsonschema/core/AbstractJsonValueSchema.java | 5 +++++ .../jsonschema/core/DefaultJsonSubSchema.java | 5 +++++ .../jsonschema/core/DefaultJsonObjectSchemaTest.java | 8 ++++++++ .../jsonschema/core/DefaultJsonSubSchemaTest.java | 11 +++++++++++ .../jsonschema/core/EmptyJsonSchemaTest.java | 5 +++++ .../jsonschema/core/FalseJsonSchemaTest.java | 6 ++++++ 6 files changed, 40 insertions(+) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java index 09a7ea53..a916aa78 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java @@ -51,4 +51,9 @@ public final JsonObject asJsonObject() { public final JsonArray asJsonArray() { return value.asJsonArray(); } + + @Override + public String toString() { + return value.toString(); + } } 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 a147356a..6646d464 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 @@ -107,4 +107,9 @@ public ValueType getValueType() { public JsonObject asJsonObject() { return schema.asJsonObject(); } + + @Override + public String toString() { + return schema.toString(); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java index dc638271..e484a110 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java @@ -145,4 +145,12 @@ void should_return_empty_if_given_name_not_resolve_to_a_valid_schematype() { void should_be_printable() { assertThat(schema.printOn(new HashMapMedia()), allOf(hasEntry("type", "array"), hasKey("items"))); } + + @Test + void should_has_a_nice_to_string() { + assertThat( + new DefaultJsonObjectSchema(Json.createObjectBuilder().add("type", "string").build()).toString(), + is("{\"type\":\"string\"}") + ); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java index 73a9720d..75c65dfd 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java @@ -109,4 +109,15 @@ void should_be_printable() { hasEntry("type", "array") ); } + + @Test + void should_has_a_nice_to_string() { + assertThat( + new DefaultJsonSubSchema( + new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("test", JsonValue.TRUE).build()), + new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("type", "array").build()) + ).toString(), + is("{\"type\":\"array\"}") + ); + } } 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 93d6fd4a..8d106dd8 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 @@ -45,4 +45,9 @@ void should_be_valid_for_everything(final JsonValue value) { void should_be_printable() { assertThat(new EmptyJsonSchema().printOn(new HashMapMedia()), anEmptyMap()); } + + @Test + void should_has_a_nice_to_string() { + assertThat(new EmptyJsonSchema().toString(), is("{}")); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java index cdd8215c..496213e3 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java @@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.not; import jakarta.json.JsonValue; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -38,4 +39,9 @@ class FalseJsonSchemaTest { void should_be_invalid_for_everything(final JsonValue value) { assertThat(new FalseJsonSchema().validator().isValid(value), is(not(true))); } + + @Test + void should_has_a_nice_to_string() { + assertThat(new FalseJsonSchema().toString(), is("false")); + } } From 82f0efc32b926895452062e37e62a4b05db57e68 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:35:05 +0200 Subject: [PATCH 10/22] rename formatvocb to formatannotationvocb --- .../jsonschema/core/vocab/OfficialVocabularies.java | 4 ++-- ...{FormatVocabulary.java => FormatAnnotationVocabulary.java} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/{FormatVocabulary.java => FormatAnnotationVocabulary.java} (94%) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java index 306bde21..78d53762 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java @@ -27,7 +27,7 @@ import io.github.sebastiantoepfer.jsonschema.core.vocab.applicator.ApplicatorVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.content.ContentVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; -import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatAnnotationVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.meta.MetaDataVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.unevaluated.UnevaluatedVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.validation.ValidationVocabulary; @@ -49,7 +49,7 @@ public final class OfficialVocabularies implements LazyVocabularies { new ApplicatorVocabulary(), new ValidationVocabulary(JSONP), new MetaDataVocabulary(), - new FormatVocabulary(), + new FormatAnnotationVocabulary(), new UnevaluatedVocabulary(), new ContentVocabulary() ); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java similarity index 94% rename from core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java rename to core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java index 38cc30b5..4b55ac37 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java @@ -36,11 +36,11 @@ * source: https://www.learnjsonschema.com/2020-12/format-annotation/ * spec: https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7.2.1 */ -public final class FormatVocabulary implements Vocabulary { +public final class FormatAnnotationVocabulary implements Vocabulary { private final Vocabulary vocab; - public FormatVocabulary() { + public FormatAnnotationVocabulary() { this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/format-annotation")); } From f73ed9791348d822ed06db88f9ac5aa9803d6d3d Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:35:23 +0200 Subject: [PATCH 11/22] enable all default schemas at default --- .../sebastiantoepfer/jsonschema/core/Keywords.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java index 8ee64c12..a324741d 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java @@ -29,7 +29,11 @@ import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.applicator.ApplicatorVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.content.ContentVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatAnnotationVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.meta.MetaDataVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.unevaluated.UnevaluatedVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.validation.ValidationVocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; @@ -54,7 +58,14 @@ final class Keywords { .stream() .collect(toMap(Vocabulary::id, Function.identity())); - DEFAULT_VOCABS = List.of(new ValidationVocabulary(JsonProvider.provider()), new ApplicatorVocabulary()); + DEFAULT_VOCABS = List.of( + new ApplicatorVocabulary(), + new ValidationVocabulary(JsonProvider.provider()), + new MetaDataVocabulary(), + new FormatAnnotationVocabulary(), + new UnevaluatedVocabulary(), + new ContentVocabulary() + ); } private final Collection vocabularies; From b84f3bfa21e38c9b69e6e925c94f2dd5225e1652 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:59:21 +0200 Subject: [PATCH 12/22] remove unused private method --- .../jsonschema/core/vocab/core/RefKeywordTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java index dc34c53c..489e2f0c 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java @@ -31,13 +31,10 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; -import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -112,13 +109,4 @@ void should_be_printable() { (Matcher) hasEntry(is("$ref"), is("#")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - final JsonSchema schema = new DefaultJsonSchemaFactory().create(json); - return new StringKeywordType( - JsonProvider.provider(), - "$ref", - s -> new RefKeyword(schema, URI.create(s)) - ).createKeyword(schema); - } } From fc81cdc0ae2e65f6949a3778ec4e8633db366f7a Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Wed, 29 May 2024 22:35:04 +0200 Subject: [PATCH 13/22] add support for dependetSchemas keyword --- core/pom.xml | 1 + .../applicator/ApplicatorVocabulary.java | 2 +- .../applicator/DependentSchemasKeyword.java | 69 +++++++++ .../DependentSchemasKeywordTest.java | 132 ++++++++++++++++++ .../PatternPropertiesKeywordTest.java | 1 - 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index 713b9a52..5fce5ef1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -128,6 +128,7 @@ **/tests/draft2020-12/const.json **/tests/draft2020-12/contains.json **/tests/draft2020-12/dependentRequired.json + **/tests/draft2020-12/dependentSchemas.json **/tests/draft2020-12/enum.json **/tests/draft2020-12/exclusiveMaximum.json **/tests/draft2020-12/exclusiveMinimum.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 7d71bcf7..0f19c90c 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -57,9 +57,9 @@ public ApplicatorVocabulary() { new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), + new NamedJsonSchemaKeywordType(DependentSchemasKeyword.NAME, DependentSchemasKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, List.of( diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java new file mode 100644 index 00000000..eda475eb --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java @@ -0,0 +1,69 @@ +/* + * 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.vocab.applicator; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.InstanceType; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import java.util.Objects; +import java.util.Optional; + +final class DependentSchemasKeyword implements Applicator { + + static final String NAME = "dependentSchemas"; + private final NamedJsonSchemas schemas; + + public DependentSchemasKeyword(final NamedJsonSchemas schemas) { + this.schemas = schemas; + } + + @Override + public boolean applyTo(final JsonValue instance) { + return !InstanceType.OBJECT.isInstance(instance) || applyTo(instance.asJsonObject()); + } + + private boolean applyTo(final JsonObject instance) { + return instance + .keySet() + .stream() + .map(schemas::schemaWithName) + .flatMap(Optional::stream) + .map(JsonSchema::validator) + .allMatch(v -> v.isValid(instance)); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schemas); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java new file mode 100644 index 00000000..0d064ad7 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java @@ -0,0 +1,132 @@ +/* + * The MIT License + * + * Copyright 2023 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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class DependentSchemasKeywordTest { + + @Test + void should_know_his_name() { + final Keyword keyword = createKeywordFrom( + Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build() + ); + + assertThat(keyword.hasName("dependentSchemas"), is(true)); + assertThat(keyword.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add("dependentSchemas", Json.createObjectBuilder().add("foo", JsonValue.TRUE)) + .build() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("dependentSchemas"), hasEntry(is("foo"), anEmptyMap())) + ); + } + + @Test + void should_be_valid_for_non_object() { + assertThat( + createKeywordFrom(Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build()) + .asApplicator() + .applyTo(JsonValue.FALSE), + is(true) + ); + } + + @Test + void should_be_valid_for_empty_object() { + assertThat( + createKeywordFrom(Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build()) + .asApplicator() + .applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(true) + ); + } + + @Test + void should_be_valid_if_property_not_present() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.FALSE)) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 1).build()), + is(true) + ); + } + + @Test + void should_be_valid_if_property_is_present_and_schema_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.TRUE)) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("true", 1).build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_property_is_present_and_schema_not_apply() { + assertThat( + createKeywordFrom( + Json.createObjectBuilder() + .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.FALSE)) + .build() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("true", 1).build()), + is(true) + ); + } + + private static Keyword createKeywordFrom(final JsonObject json) { + return new NamedJsonSchemaKeywordType("dependentSchemas", DependentSchemasKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java index 9d8dc219..88245040 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java @@ -27,7 +27,6 @@ import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; From 4d3c4459da8c609ac14ab859fecba986c7978fa5 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Sat, 8 Jun 2024 14:11:22 +0200 Subject: [PATCH 14/22] add some more and use img.shield.io for all badges --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94317dbd..3f9e87b0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=sebastian-toepfer_json-schema&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=sebastian-toepfer_json-schema) -[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#io.github.sebastian-toepfer.json-schema:json-schema) +![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) +![Sonar Tests](https://img.shields.io/sonar/tests/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) +![Sonar Violations](https://img.shields.io/sonar/violations/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) + +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/sebastian-toepfer/json-schema/build.yml) + +![Maven Central Version](https://img.shields.io/maven-central/v/io.github.sebastian-toepfer.json-schema/json-schema) +![GitHub Release](https://img.shields.io/github/v/release/sebastian-toepfer/json-schema) +![GitHub commits since latest release](https://img.shields.io/github/commits-since/sebastian-toepfer/json-schema/latest) + +[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/io/github/sebastian-toepfer/json-schema/json-schema/README.md) # json-schema json schema for json-api From 3c90ecb6c66011f383f364c8f0520c2359d4ea79 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:46:26 +0200 Subject: [PATCH 15/22] use keyword relationship for additionalproperties keyword --- .../AdditionalPropertiesKeyword.java | 25 ++++++++++--------- .../applicator/ApplicatorVocabulary.java | 17 ++++++++++++- .../AdditionalPropertiesKeywordTest.java | 23 ++++++++++++----- 3 files changed, 46 insertions(+), 19 deletions(-) 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 85714078..52571818 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 @@ -23,9 +23,11 @@ */ package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator; +import static java.util.function.Predicate.not; + import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.InstanceType; -import io.github.sebastiantoepfer.jsonschema.JsonSubSchema; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; @@ -38,8 +40,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; import java.util.stream.Stream; /** @@ -61,10 +61,15 @@ final class AdditionalPropertiesKeyword implements Applicator, Annotation { static final String NAME = "additionalProperties"; - private final JsonSubSchema additionalPropertiesSchema; + private final Collection affectedBy; + private final JsonSchema additionalPropertiesSchema; - public AdditionalPropertiesKeyword(final JsonSubSchema additionalPropertiesSchema) { + public AdditionalPropertiesKeyword( + final Collection affectedBy, + final JsonSchema additionalPropertiesSchema + ) { this.additionalPropertiesSchema = additionalPropertiesSchema; + this.affectedBy = List.copyOf(affectedBy); } @Override @@ -93,16 +98,12 @@ public JsonValue valueFor(final JsonValue instance) { private Stream> findPropertiesForValidation(final JsonObject instance) { final Collection ignoredProperties = findPropertyNamesAlreadyConveredByOthersIn(instance); - return instance.entrySet().stream().filter(Predicate.not(e -> ignoredProperties.contains(e.getKey()))); + return instance.entrySet().stream().filter(not(e -> ignoredProperties.contains(e.getKey()))); } private Collection findPropertyNamesAlreadyConveredByOthersIn(final JsonValue instance) { - return Stream.of( - additionalPropertiesSchema.owner().keywordByName("properties"), - additionalPropertiesSchema.owner().keywordByName("patternProperties") - ) - .flatMap(Optional::stream) - .map(Keyword::asAnnotation) + return affectedBy + .stream() .map(anno -> anno.valueFor(instance)) .map(JsonValue::asJsonArray) .flatMap(Collection::stream) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 0f19c90c..ad18b208 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -27,11 +27,14 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectByType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedBy; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import jakarta.json.JsonValue; import java.net.URI; import java.util.List; import java.util.Optional; @@ -55,7 +58,19 @@ public ApplicatorVocabulary() { new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new), new SubSchemaKeywordType(NotKeyword.NAME, NotKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), - new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), + //nomally affectedBy ... but we had the needed function only in affects :( + new AffectsKeywordType( + AdditionalPropertiesKeyword.NAME, + List.of( + new Affects("properties", JsonValue.EMPTY_JSON_ARRAY), + new Affects("patternProperties", JsonValue.EMPTY_JSON_ARRAY) + ), + (affects, schema) -> + new SubSchemaKeywordType( + AdditionalPropertiesKeyword.NAME, + s -> new AdditionalPropertiesKeyword(affects, s) + ).createKeyword(schema) + ), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new NamedJsonSchemaKeywordType(DependentSchemasKeyword.NAME, DependentSchemasKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java index 053791be..eeed5958 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java @@ -30,12 +30,16 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +47,7 @@ class AdditionalPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new AdditionalPropertiesKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)); assertThat(keyword.hasName("additionalProperties"), is(true)); assertThat(keyword.hasName("test"), is(false)); } @@ -147,8 +149,17 @@ void should_be_printable() { } private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("additionalProperties", AdditionalPropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); + return new AffectsKeywordType( + "additionalProperties", + List.of( + new Affects("properties", JsonValue.EMPTY_JSON_ARRAY), + new Affects("patternProperties", JsonValue.EMPTY_JSON_ARRAY) + ), + (affects, schema) -> + new SubSchemaKeywordType( + "additionalProperties", + s -> new AdditionalPropertiesKeyword(affects, s) + ).createKeyword(schema) + ).createKeyword(new DefaultJsonSchemaFactory().create(json)); } } From cfa5be433524edaa74988c8238404c7e330eeaa8 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:46:26 +0200 Subject: [PATCH 16/22] use keyword relationship for items keyword --- .../keyword/type/SubSchemaKeywordType.java | 2 +- .../applicator/ApplicatorVocabulary.java | 21 +++++++++++- .../core/vocab/applicator/ItemsKeyword.java | 34 ++++++++++--------- .../vocab/applicator/ItemsKeywordTest.java | 29 +++++++++++----- 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SubSchemaKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SubSchemaKeywordType.java index 12f94047..4b8cadc3 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SubSchemaKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SubSchemaKeywordType.java @@ -47,6 +47,6 @@ public String name() { @Override public Keyword createKeyword(final JsonSchema schema) { - return schema.asSubSchema(name).map(keywordCreator).orElseThrow(IllegalArgumentException::new); + return schema.asSubSchema(name()).map(keywordCreator).orElseThrow(IllegalArgumentException::new); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index ad18b208..5c65bb01 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -34,6 +34,7 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import jakarta.json.Json; import jakarta.json.JsonValue; import java.net.URI; import java.util.List; @@ -73,7 +74,25 @@ public ApplicatorVocabulary() { ), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new NamedJsonSchemaKeywordType(DependentSchemasKeyword.NAME, DependentSchemasKeyword::new), - new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), + //this example shows my missunderstanding from affects, affectedBy and keywordtypes :( + new AffectedByKeywordType( + ItemsKeyword.NAME, + List.of( + new AffectedBy(AffectByType.EXTENDS, "minItems"), + new AffectedBy(AffectByType.EXTENDS, "maxItems") + ), + //nomally affectedBy too ... but we had the needed function only in affects :( + schema -> + new AffectsKeywordType( + ItemsKeyword.NAME, + List.of(new Affects("prefixItems", Json.createValue(-1))), + (affects, subSchema) -> + new SubSchemaKeywordType( + ItemsKeyword.NAME, + s -> new ItemsKeyword(affects, s) + ).createKeyword(subSchema) + ).createKeyword(schema) + ), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, 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 2a8acd31..e20494b6 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 @@ -25,17 +25,16 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.InstanceType; -import io.github.sebastiantoepfer.jsonschema.JsonSubSchema; -import io.github.sebastiantoepfer.jsonschema.Validator; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.JsonArray; import jakarta.json.JsonNumber; import jakarta.json.JsonValue; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; /** * items : Schema
@@ -56,9 +55,11 @@ final class ItemsKeyword implements Applicator, Annotation { static final String NAME = "items"; - private final JsonSubSchema schema; + private final Collection affectedBys; + private final JsonSchema schema; - public ItemsKeyword(final JsonSubSchema schema) { + public ItemsKeyword(final Collection affectedBys, final JsonSchema schema) { + this.affectedBys = List.copyOf(affectedBys); this.schema = Objects.requireNonNull(schema); } @@ -89,28 +90,29 @@ public JsonValue valueFor(final JsonValue value) { } private boolean appliesToAnyFor(final JsonArray value) { - return startIndexFor(value) == -1; + return itemsForValidation(value).anyMatch(schema.validator()::isValid); } @Override public boolean applyTo(final JsonValue instance) { - return !InstanceType.ARRAY.isInstance(instance) || matchesSchema(instance.asJsonArray()); + return !InstanceType.ARRAY.isInstance(instance) || applyTo(instance.asJsonArray()); } - private boolean matchesSchema(final JsonArray items) { - final Validator itemValidator = schema.validator(); - return items.stream().skip(startIndexFor(items) + 1L).allMatch(itemValidator::isValid); + private boolean applyTo(final JsonArray items) { + return itemsForValidation(items).allMatch(schema.validator()::isValid); + } + + private Stream itemsForValidation(final JsonArray items) { + return items.stream().skip(startIndexFor(items) + 1L); } private int startIndexFor(final JsonArray value) { - return schema - .owner() - .keywordByName("prefixItems") - .map(Keyword::asAnnotation) + return affectedBys + .stream() .map(anno -> anno.valueFor(value)) .map(v -> new MaxIndexCalculator(value, v)) - .map(MaxIndexCalculator::maxIndex) - .orElse(-1); + .mapToInt(MaxIndexCalculator::maxIndex) + .sum(); } private static class MaxIndexCalculator { diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java index a346d3c0..f7007d0a 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java @@ -27,22 +27,26 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectByType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedBy; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.junit.jupiter.api.Test; class ItemsKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build() - ); - + final Keyword items = new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)); assertThat(items.hasName("items"), is(true)); assertThat(items.hasName("test"), is(false)); } @@ -84,7 +88,7 @@ void should_be_applicator_and_annotation() { @Test void should_produces_true_if_is_applied_to_any_instance() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build()) + createKeywordFrom(Json.createObjectBuilder().add("items", JsonValue.TRUE).build()) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).build()), is(JsonValue.TRUE) @@ -137,8 +141,17 @@ void should_be_invalid_if_invaliditem_is_not_already_checked_by_prefixItems() { } private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("items", ItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); + return new AffectedByKeywordType( + "items", + List.of(new AffectedBy(AffectByType.EXTENDS, "minItems"), new AffectedBy(AffectByType.EXTENDS, "maxItems")), + //nomally affectedBy too ... but we had the needed function only in affects :( + schema -> + new AffectsKeywordType( + "items", + List.of(new Affects("prefixItems", Json.createValue(-1))), + (affects, inner_schema) -> + new SubSchemaKeywordType("items", s -> new ItemsKeyword(affects, s)).createKeyword(inner_schema) + ).createKeyword(schema) + ).createKeyword(new DefaultJsonSchemaFactory().create(json)); } } From b2f1f2032ddca9ec542fe743013a93f62bbaa655 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 20:04:16 +0200 Subject: [PATCH 17/22] refactor all test of keywords defined by applicator vocab --- .../core/vocab/applicator/ItemsKeyword.java | 18 ++- .../AdditionalPropertiesKeywordTest.java | 72 +++------ .../vocab/applicator/AllOfKeywordTest.java | 100 ++++-------- .../vocab/applicator/AnyOfKeywordTest.java | 58 ++++--- .../vocab/applicator/ContainsKeywordTest.java | 59 ++----- .../DependentSchemasKeywordTest.java | 48 ++---- .../vocab/applicator/ItemsKeywordTest.java | 62 ++------ .../core/vocab/applicator/NotKeywordTest.java | 38 ++--- .../vocab/applicator/OneOfKeywordTest.java | 147 ++++++++---------- .../PatternPropertiesKeywordTest.java | 76 +++------ .../applicator/PrefixItemsKeywordTest.java | 55 +++---- .../applicator/PropertiesKeywordTest.java | 55 ++----- 12 files changed, 258 insertions(+), 530 deletions(-) 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 e20494b6..0591c7ed 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 @@ -107,12 +107,18 @@ private Stream itemsForValidation(final JsonArray items) { } private int startIndexFor(final JsonArray value) { - return affectedBys - .stream() - .map(anno -> anno.valueFor(value)) - .map(v -> new MaxIndexCalculator(value, v)) - .mapToInt(MaxIndexCalculator::maxIndex) - .sum(); + final int result; + if (affectedBys.isEmpty()) { + result = -1; + } else { + result = affectedBys + .stream() + .map(anno -> anno.valueFor(value)) + .map(v -> new MaxIndexCalculator(value, v)) + .mapToInt(MaxIndexCalculator::maxIndex) + .sum(); + } + return result; } private static class MaxIndexCalculator { diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java index eeed5958..711ca283 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java @@ -31,13 +31,9 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.JsonSchemas; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import java.util.List; import org.hamcrest.Matcher; @@ -55,11 +51,9 @@ void should_know_his_name() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", JsonValue.EMPTY_JSON_ARRAY)), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_ARRAY), @@ -70,11 +64,9 @@ void should_be_valid_for_non_objects() { @Test void should_not_valid_if_no_additionals_are_allow() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()), @@ -85,11 +77,9 @@ void should_not_valid_if_no_additionals_are_allow() { @Test void should_valid_if_additionals_are_allow() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.TRUE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.TRUE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()), @@ -100,11 +90,9 @@ void should_valid_if_additionals_are_allow() { @Test void should_valid_if_no_additionals_are_allow_and_no_additionals_their() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), @@ -115,8 +103,9 @@ void should_valid_if_no_additionals_are_allow_and_no_additionals_their() { @Test void should_be_an_applicator_and_an_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.TRUE).build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", JsonValue.EMPTY_JSON_ARRAY)), + JsonSchemas.load(JsonValue.FALSE) ).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); @@ -125,11 +114,9 @@ void should_be_an_applicator_and_an_annotation() { @Test void should_return_propertynames_which_will_be_validated() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.TRUE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.TRUE) ) .asAnnotation() .valueFor(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()) @@ -141,25 +128,10 @@ void should_return_propertynames_which_will_be_validated() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ).printOn(new HashMapMedia()), + new AdditionalPropertiesKeyword(List.of(), JsonSchemas.load(JsonValue.EMPTY_JSON_OBJECT)).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("additionalProperties"), anEmptyMap()) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectsKeywordType( - "additionalProperties", - List.of( - new Affects("properties", JsonValue.EMPTY_JSON_ARRAY), - new Affects("patternProperties", JsonValue.EMPTY_JSON_ARRAY) - ), - (affects, schema) -> - new SubSchemaKeywordType( - "additionalProperties", - s -> new AdditionalPropertiesKeyword(affects, s) - ).createKeyword(schema) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java index 940908cf..86bc7560 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java @@ -25,16 +25,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,10 +41,9 @@ class AllOfKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("allOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() + final Keyword items = new AllOfKeyword( + Json.createArrayBuilder().add(JsonValue.TRUE).build().stream().map(JsonSchemas::load).toList() ); - assertThat(items.hasName("allOf"), is(true)); assertThat(items.hasName("test"), is(false)); } @@ -54,56 +51,30 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder() - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) - ) - ) - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) - ) - ) - ) + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) .build() + .stream() + .map(JsonSchemas::load) + .toList() ).printOn(new HashMapMedia()), - (Matcher) hasEntry(is("allOf"), hasItem((hasKey("allOf")))) + (Matcher) hasEntry(is("allOf"), hasItems(hasKey("type"), hasKey("minimum"))) ); } @Test void should_be_valid_if_all_schemas_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder() - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) - ) - ) - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) - ) - ) - ) + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createValue(25)), @@ -114,37 +85,18 @@ void should_be_valid_if_all_schemas_applies() { @Test void should_be_invalid_if_any_schemas_not_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder() - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) - ) - ) - .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("minimum", 18)) - ) - ) - ) + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createValue(10)), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SchemaArrayKeywordType("allOf", AllOfKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java index 7befdafb..f7fa853e 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java @@ -30,12 +30,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +42,7 @@ class AnyOfKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("anyOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ); + final Keyword items = new AnyOfKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); assertThat(items.hasName("anyOf"), is(true)); assertThat(items.hasName("test"), is(false)); @@ -54,20 +51,17 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "anyOf", - Json.createArrayBuilder() + new AnyOfKeyword( + List.of( + JsonSchemas.load( + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "allOf", - Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) - ) + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) ) + .build() ) - .build() + ) ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("anyOf"), hasItem((hasKey("allOf")))) ); @@ -76,13 +70,15 @@ void should_be_printable() { @Test void should_be_valid_if_any_schemas_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "anyOf", - Json.createArrayBuilder().add(JsonValue.FALSE).add(JsonValue.TRUE).add(JsonValue.FALSE) - ) + new AnyOfKeyword( + Json.createArrayBuilder() + .add(JsonValue.FALSE) + .add(JsonValue.TRUE) + .add(JsonValue.FALSE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createValue(25)), @@ -93,20 +89,18 @@ void should_be_valid_if_any_schemas_applies() { @Test void should_be_invalid_if_no_schemas_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("anyOf", Json.createArrayBuilder().add(JsonValue.FALSE).add(JsonValue.FALSE)) + new AnyOfKeyword( + Json.createArrayBuilder() + .add(JsonValue.FALSE) + .add(JsonValue.FALSE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createValue(10)), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SchemaArrayKeywordType("anyOf", AnyOfKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java index 02c8c5f8..f94a4c35 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java @@ -31,11 +31,8 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.JsonSchemas; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -46,9 +43,9 @@ class ContainsKeywordTest { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ).printOn(new HashMapMedia()), + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("contains"), hasEntry(is("type"), is("number"))) ); } @@ -64,8 +61,8 @@ void should_know_his_name() { @Test void should_be_an_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() + new ContainsKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build()) ).categories(), Matchers.containsInAnyOrder(Keyword.KeywordCategory.ANNOTATION, Keyword.KeywordCategory.APPLICATOR) ); @@ -74,9 +71,7 @@ void should_be_an_applicator_and_annotation() { @Test void should_apply_for_non_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -86,9 +81,7 @@ void should_apply_for_non_array() { @Test void should_not_apply_to_empty_array_if_non_min_andor_max_is_provided() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_ARRAY), is(false) @@ -98,9 +91,7 @@ void should_not_apply_to_empty_array_if_non_min_andor_max_is_provided() { @Test void should_apply_if_one_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo( Json.createArrayBuilder() @@ -118,9 +109,7 @@ void should_apply_if_one_item_applies() { @Test void should_apply_if_all_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "string")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "string").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(true) @@ -130,9 +119,7 @@ void should_apply_if_all_item_applies() { @Test void should_not_apply_if_non_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo( Json.createArrayBuilder().add("foo").add(false).add(Json.createArrayBuilder().add("bar")).build() @@ -144,9 +131,7 @@ void should_not_apply_if_non_item_applies() { @Test void should_return_false_for_non_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(JsonValue.EMPTY_JSON_OBJECT), is(JsonValue.FALSE) @@ -156,9 +141,7 @@ void should_return_false_for_non_array() { @Test void should_return_empty_array_if_no_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(Json.createArrayBuilder().add("foo").build()) .asJsonArray(), @@ -169,9 +152,7 @@ void should_return_empty_array_if_no_item_applies() { @Test void should_return_empty_array_for_empty_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(JsonValue.EMPTY_JSON_ARRAY) .asJsonArray(), @@ -182,9 +163,7 @@ void should_return_empty_array_for_empty_array() { @Test void should_return_matching_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor( Json.createArrayBuilder() @@ -203,18 +182,10 @@ void should_return_matching_items() { @Test void should_return_true_if_all_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "string")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "string").build())) .asAnnotation() .valueFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(JsonValue.TRUE) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("contains", ContainsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java index 0d064ad7..e0b1b7b4 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java @@ -29,12 +29,12 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.Map; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +42,7 @@ class DependentSchemasKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())); assertThat(keyword.hasName("dependentSchemas"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -53,11 +51,9 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("dependentSchemas", Json.createObjectBuilder().add("foo", JsonValue.TRUE)) - .build() - ).printOn(new HashMapMedia()), + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("foo", JsonSchemas.load(JsonValue.TRUE)))).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("dependentSchemas"), hasEntry(is("foo"), anEmptyMap())) ); } @@ -65,9 +61,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build()) - .asApplicator() - .applyTo(JsonValue.FALSE), + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.FALSE), is(true) ); } @@ -75,7 +69,7 @@ void should_be_valid_for_non_object() { @Test void should_be_valid_for_empty_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("dependentSchemas", JsonValue.EMPTY_JSON_OBJECT).build()) + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -85,11 +79,7 @@ void should_be_valid_for_empty_object() { @Test void should_be_valid_if_property_not_present() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", 1).build()), is(true) @@ -99,11 +89,7 @@ void should_be_valid_if_property_not_present() { @Test void should_be_valid_if_property_is_present_and_schema_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .build() - ) + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("true", 1).build()), is(true) @@ -113,20 +99,10 @@ void should_be_valid_if_property_is_present_and_schema_apply() { @Test void should_be_invalid_if_property_is_present_and_schema_not_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("dependentSchemas", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("true", 1).build()), is(true) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType("dependentSchemas", DependentSchemasKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java index f7007d0a..ef61862d 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java @@ -28,16 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.jsonschema.JsonSchemas; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectByType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedBy; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import java.util.List; import org.junit.jupiter.api.Test; @@ -54,9 +47,7 @@ void should_know_his_name() { @Test void should_be_invalid_if_items_does_not_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", Json.createObjectBuilder().add("type", "number")).build() - ) + new ItemsKeyword(List.of(), JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add("invalid").add(2).build()), is(false) @@ -66,9 +57,7 @@ void should_be_invalid_if_items_does_not_match_schema() { @Test void should_be_valid_if_all_items_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", Json.createObjectBuilder().add("type", "number")).build() - ) + new ItemsKeyword(List.of(), JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -78,9 +67,7 @@ void should_be_valid_if_all_items_match_schema() { @Test void should_be_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.EMPTY_JSON_OBJECT)).categories(), contains(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -88,7 +75,7 @@ void should_be_applicator_and_annotation() { @Test void should_produces_true_if_is_applied_to_any_instance() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("items", JsonValue.TRUE).build()) + new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).build()), is(JsonValue.TRUE) @@ -98,11 +85,9 @@ void should_produces_true_if_is_applied_to_any_instance() { @Test void should_return_false_if_not_applies_to_any_item() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true)) - .add("items", JsonValue.FALSE) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", JsonValue.TRUE)), + JsonSchemas.load(JsonValue.FALSE) ) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).build()), @@ -113,11 +98,9 @@ void should_return_false_if_not_applies_to_any_item() { @Test void should_be_valid_if_invaliditem_is_already_checked_by_prefixItems() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true).add(true)) - .add("items", Json.createObjectBuilder().add("type", "integer")) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", JsonValue.TRUE)), + JsonSchemas.load(Json.createObjectBuilder().add("type", "integer").build()) ) .asApplicator() .applyTo(Json.createArrayBuilder().add("1").add("2").add(1).build()), @@ -128,30 +111,13 @@ void should_be_valid_if_invaliditem_is_already_checked_by_prefixItems() { @Test void should_be_invalid_if_invaliditem_is_not_already_checked_by_prefixItems() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true)) - .add("items", Json.createObjectBuilder().add("type", "integer")) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", Json.createValue(0))), + JsonSchemas.load(Json.createObjectBuilder().add("type", "integer").build()) ) .asApplicator() .applyTo(Json.createArrayBuilder().add("1").add("2").add(1).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectedByKeywordType( - "items", - List.of(new AffectedBy(AffectByType.EXTENDS, "minItems"), new AffectedBy(AffectByType.EXTENDS, "maxItems")), - //nomally affectedBy too ... but we had the needed function only in affects :( - schema -> - new AffectsKeywordType( - "items", - List.of(new Affects("prefixItems", Json.createValue(-1))), - (affects, inner_schema) -> - new SubSchemaKeywordType("items", s -> new ItemsKeyword(affects, s)).createKeyword(inner_schema) - ).createKeyword(schema) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java index f03d810d..28cc6849 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java @@ -29,11 +29,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,7 +40,7 @@ class NotKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.FALSE).build()); + final Keyword items = new NotKeyword(JsonSchemas.load(JsonValue.FALSE)); assertThat(items.hasName("not"), is(true)); assertThat(items.hasName("test"), is(false)); @@ -51,18 +49,16 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "not", - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) - ) - .add("required", Json.createArrayBuilder().add("foo")) - ) - .build() + new NotKeyword( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + .build() + ) ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("not"), hasKey("properties")) ); @@ -71,7 +67,7 @@ void should_be_printable() { @Test void should_be_valid_if_schema_not_apply() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.FALSE).build()) + new NotKeyword(JsonSchemas.load(JsonValue.FALSE)) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), is(true) @@ -81,16 +77,10 @@ void should_be_valid_if_schema_not_apply() { @Test void should_be_invalid_if_schema_apply() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("not", JsonValue.TRUE).build()) + new NotKeyword(JsonSchemas.load(JsonValue.TRUE)) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("not", NotKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java index 94315583..62b9f8da 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java @@ -30,12 +30,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +42,7 @@ class OneOfKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("oneOf", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ); + final Keyword items = new OneOfKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); assertThat(items.hasName("oneOf"), is(true)); assertThat(items.hasName("test"), is(false)); @@ -54,31 +51,28 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() + new OneOfKeyword( + Json.createArrayBuilder() .add( - "oneOf", - Json.createArrayBuilder() + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("foo", Json.createObjectBuilder().add("type", "string")) - ) - .add("required", Json.createArrayBuilder().add("foo")) + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("bar", Json.createObjectBuilder().add("type", "number")) - ) - .add("required", Json.createArrayBuilder().add("bar")) + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) ) + .add("required", Json.createArrayBuilder().add("bar")) ) .build() + .stream() + .map(JsonSchemas::load) + .toList() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("oneOf"), hasItem((hasKey("properties")))) ); @@ -87,31 +81,28 @@ void should_be_printable() { @Test void should_be_valid_if_exactly_one_schema_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() + new OneOfKeyword( + Json.createArrayBuilder() .add( - "oneOf", - Json.createArrayBuilder() + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("foo", Json.createObjectBuilder().add("type", "string")) - ) - .add("required", Json.createArrayBuilder().add("foo")) + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("bar", Json.createObjectBuilder().add("type", "number")) - ) - .add("required", Json.createArrayBuilder().add("bar")) + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) ) + .add("required", Json.createArrayBuilder().add("bar")) ) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), @@ -122,31 +113,28 @@ void should_be_valid_if_exactly_one_schema_applies() { @Test void should_be_invalid_if_none_schema_not_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() + new OneOfKeyword( + Json.createArrayBuilder() .add( - "oneOf", - Json.createArrayBuilder() + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("foo", Json.createObjectBuilder().add("type", "string")) - ) - .add("required", Json.createArrayBuilder().add("foo")) + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("bar", Json.createObjectBuilder().add("type", "number")) - ) - .add("required", Json.createArrayBuilder().add("bar")) + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) ) + .add("required", Json.createArrayBuilder().add("bar")) ) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), @@ -157,41 +145,32 @@ void should_be_invalid_if_none_schema_not_apply() { @Test void should_be_invalid_if_more_than_one_schema_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() + new OneOfKeyword( + Json.createArrayBuilder() .add( - "oneOf", - Json.createArrayBuilder() + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("foo", Json.createObjectBuilder().add("type", "string")) - ) - .add("required", Json.createArrayBuilder().add("foo")) + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() .add( - Json.createObjectBuilder() - .add( - "properties", - Json.createObjectBuilder() - .add("bar", Json.createObjectBuilder().add("type", "number")) - ) - .add("required", Json.createArrayBuilder().add("bar")) + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) ) + .add("required", Json.createArrayBuilder().add("bar")) ) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", "foo").add("bar", 33).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SchemaArrayKeywordType("oneOf", OneOfKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java index 88245040..f015d7c9 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java @@ -30,14 +30,14 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonString; import jakarta.json.JsonValue; import java.math.BigDecimal; +import java.util.Map; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -45,9 +45,7 @@ class PatternPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())); assertThat(keyword.hasName("patternProperties"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -56,9 +54,7 @@ void should_know_his_name() { @Test void should_be_an_applicator_and_an_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -66,9 +62,7 @@ void should_be_an_applicator_and_an_annotation() { @Test void should_be_valid_for_non_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build()) - .asApplicator() - .applyTo(JsonValue.FALSE), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.FALSE), is(true) ); } @@ -76,7 +70,7 @@ void should_be_valid_for_non_object() { @Test void should_be_valid_for_empty_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build()) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -86,11 +80,7 @@ void should_be_valid_for_empty_object() { @Test void should_be_valid_if_properties_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.TRUE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.TRUE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(true) @@ -100,32 +90,20 @@ void should_be_valid_if_properties_applies_to_his_schema() { @Test void should_be_invalid_if_properties_not_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.FALSE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(false) ); } - /* - Attention can be falky. - JsonObject is a map, so the properties occurred in random order. - This means for this test it could be green without using all assertions. - */ @Test - void should_be_invalid_if_one_schema_doesn_apply() { + void should_be_invalid_if_one_schema_does_not_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "patternProperties", - Json.createObjectBuilder().add("t.st", JsonValue.TRUE).add("t.*", JsonValue.FALSE) - ) - .build() + new PatternPropertiesKeyword( + new NamedJsonSchemas( + Map.of("t.st", JsonSchemas.load(JsonValue.TRUE), "t.*", JsonSchemas.load(JsonValue.FALSE)) + ) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), @@ -136,11 +114,7 @@ void should_be_invalid_if_one_schema_doesn_apply() { @Test void should_be_valid_if_properties_not_covered() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.FALSE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", 1).build()), is(true) @@ -150,11 +124,7 @@ void should_be_valid_if_properties_not_covered() { @Test void should_return_the_matching_property_names() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("f.o", JsonValue.TRUE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("f.o", JsonSchemas.load(JsonValue.TRUE)))) .asAnnotation() .valueFor( Json.createObjectBuilder() @@ -175,18 +145,10 @@ void should_return_the_matching_property_names() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("f.o", JsonValue.TRUE)) - .build() - ).printOn(new HashMapMedia()), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("f.o", JsonSchemas.load(JsonValue.TRUE)))).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("patternProperties"), hasEntry(is("f.o"), anEmptyMap())) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType("patternProperties", PatternPropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java index a433f374..9ba9728b 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java @@ -31,12 +31,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -44,9 +43,7 @@ class PrefixItemsKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ); + final Keyword items = new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); assertThat(items.hasName("prefixItems"), is(true)); assertThat(items.hasName("test"), is(false)); @@ -55,9 +52,7 @@ void should_know_his_name() { @Test void should_return_zero_as_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).add(2).build()), is(Json.createValue(0)) @@ -67,10 +62,14 @@ void should_return_zero_as_value() { @Test void should_retrun_true_if_is_applies_to_all_values() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE).add(JsonValue.TRUE)) + new PrefixItemsKeyword( + Json.createArrayBuilder() + .add(JsonValue.TRUE) + .add(JsonValue.TRUE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).add(2).build()), @@ -81,9 +80,7 @@ void should_retrun_true_if_is_applies_to_all_values() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.FALSE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.FALSE))) .asApplicator() .applyTo(Json.createValue(1)), is(true) @@ -93,9 +90,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_if_first_item_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).build()), is(true) @@ -105,10 +100,14 @@ void should_be_valid_if_first_item_match_schema() { @Test void should_be_invalid_if_second_item_does_not_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE).add(JsonValue.FALSE)) + new PrefixItemsKeyword( + Json.createArrayBuilder() + .add(JsonValue.TRUE) + .add(JsonValue.FALSE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add(3).build()), @@ -119,9 +118,7 @@ void should_be_invalid_if_second_item_does_not_match_schema() { @Test void should_be_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", JsonValue.EMPTY_JSON_ARRAY).build() - ).categories(), + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -129,16 +126,8 @@ void should_be_applicator_and_annotation() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ).printOn(new HashMapMedia()), + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))).printOn(new HashMapMedia()), (Matcher) hasEntry(is("prefixItems"), contains(anEmptyMap())) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SchemaArrayKeywordType("prefixItems", PrefixItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java index f3e88790..1d18e283 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java @@ -30,11 +30,10 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import java.util.Map; import org.hamcrest.Matcher; @@ -44,9 +43,7 @@ class PropertiesKeywordTest { @Test void should_be_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new PropertiesKeyword(new NamedJsonSchemas(Map.of())); assertThat(keyword.hasName("properties"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -55,9 +52,7 @@ void should_be_know_his_name() { @Test void should_be_an_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new PropertiesKeyword(new NamedJsonSchemas(Map.of())).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -65,9 +60,7 @@ void should_be_an_applicator_and_annotation() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build()) - .asApplicator() - .applyTo(JsonValue.EMPTY_JSON_ARRAY), + new PropertiesKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -75,11 +68,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_if_properties_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(true) @@ -89,11 +78,7 @@ void should_be_valid_if_properties_applies_to_his_schema() { @Test void should_be_invalid_if_properties_not_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(false) @@ -103,11 +88,7 @@ void should_be_invalid_if_properties_not_applies_to_his_schema() { @Test void should_be_valid_for_empty_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -117,10 +98,10 @@ void should_be_valid_for_empty_objects() { @Test void should_return_all_matched_propertynames() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", true).add("foo", true)) - .build() + new PropertiesKeyword( + new NamedJsonSchemas( + Map.of("test", JsonSchemas.load(JsonValue.TRUE), "foo", JsonSchemas.load(JsonValue.TRUE)) + ) ) .asAnnotation() .valueFor(Json.createObjectBuilder().add("foo", 1).build()) @@ -134,20 +115,10 @@ void should_return_all_matched_propertynames() { @Test void should_be_printable() { assertThat( - ((Map) createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .build() - ) + ((Map) new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) .printOn(new HashMapMedia()) .get("properties")).get("test"), (Matcher) anEmptyMap() ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType("properties", PropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } From bc9b0f180578b3c41eec23ac55173dd44396f436 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 20:17:08 +0200 Subject: [PATCH 18/22] refactor test for all keyword defined in corevocab --- .../core/vocab/core/VocabularyKeyword.java | 86 +++++++++++++++++++ .../vocab/core/VocabularyKeywordType.java | 63 +------------- .../core/vocab/core/CommentKeywordTest.java | 22 +---- .../core/vocab/core/DefsKeywordTest.java | 21 +---- .../vocab/core/DynamicRefKeywordTest.java | 35 +++----- .../core/vocab/core/IdKeywordTest.java | 27 +----- .../core/vocab/core/RefKeywordTest.java | 81 ++++++++--------- .../core/vocab/core/RefKeywordTypeTest.java | 28 ++++++ .../core/vocab/core/SchemaKeywordTest.java | 31 +------ .../vocab/core/VocabularyKeywordTypeTest.java | 50 +++-------- .../vocabulary/spi/VocabularyDefinitions.java | 3 +- 11 files changed, 188 insertions(+), 259 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java new file mode 100644 index 00000000..7dbb20ce --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java @@ -0,0 +1,86 @@ +/* + * 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.vocab.core; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * $vocabulary : Object<URI, Boolean>
+ * This keyword is used in meta-schemas to identify the required and optional vocabularies available for use in
+ * schemas described by that meta-schema.
+ *
+ *
    + *
  • identifier
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/core/vocabulary/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.1.2 + */ +final class VocabularyKeyword implements VocabularyDefinitions { + + static final String NAME = "$vocabulary"; + private final JsonObject vocabularies; + + VocabularyKeyword(final JsonValue vocabularies) { + this(vocabularies.asJsonObject()); + } + + VocabularyKeyword(final JsonObject vocabularies) { + this.vocabularies = Objects.requireNonNull(vocabularies); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, new JsonObjectPrintable(vocabularies)); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public Collection categories() { + //is a identifier after spec ... but how to implement it as it? + return List.of(); + } + + @Override + public Stream definitions() { + return vocabularies + .entrySet() + .stream() + .map(entry -> new VocabularyDefinition(URI.create(entry.getKey()), entry.getValue() == JsonValue.TRUE)); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java index 1bbac9ae..e099babb 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java @@ -23,21 +23,11 @@ */ package io.github.sebastiantoepfer.jsonschema.core.vocab.core; -import io.github.sebastiantoepfer.ddd.common.Media; -import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; import io.github.sebastiantoepfer.jsonschema.InstanceType; import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import java.net.URI; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; /** * see: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-vocabulary-keyword @@ -46,11 +36,11 @@ public final class VocabularyKeywordType implements KeywordType { @Override public String name() { - return "$vocabulary"; + return VocabularyKeyword.NAME; } @Override - public VocabularyKeyword createKeyword(final JsonSchema schema) { + public VocabularyDefinitions createKeyword(final JsonSchema schema) { final JsonValue value = schema.asJsonObject().get((name())); final VocabularyKeyword result; if (InstanceType.OBJECT.isInstance(value)) { @@ -64,53 +54,4 @@ public VocabularyKeyword createKeyword(final JsonSchema schema) { } return result; } - - /** - * $vocabulary : Object<URI, Boolean>
- * This keyword is used in meta-schemas to identify the required and optional vocabularies available for use in
- * schemas described by that meta-schema.
- *
- *
    - *
  • identifier
  • - *
- * - * source: https://www.learnjsonschema.com/2020-12/core/vocabulary/ - * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.1.2 - */ - public final class VocabularyKeyword implements Keyword, VocabularyDefinitions { - - private final JsonObject vocabularies; - - VocabularyKeyword(final JsonValue vocabularies) { - this(vocabularies.asJsonObject()); - } - - VocabularyKeyword(final JsonObject vocabularies) { - this.vocabularies = Objects.requireNonNull(vocabularies); - } - - @Override - public > T printOn(final T media) { - return media.withValue(name(), new JsonObjectPrintable(vocabularies)); - } - - @Override - public boolean hasName(final String name) { - return Objects.equals(name(), name); - } - - @Override - public Collection categories() { - //is a identifier after spec ... but how to implement it as it? - return List.of(); - } - - @Override - public Stream definitions() { - return vocabularies - .entrySet() - .stream() - .map(entry -> new VocabularyDefinition(URI.create(entry.getKey()), entry.getValue() == JsonValue.TRUE)); - } - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java index a853cda2..625e81cf 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java @@ -28,21 +28,14 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import org.junit.jupiter.api.Test; class CommentKeywordTest { @Test void should_know_her_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("$comment", Json.createValue("comment")).build() - ); + final Keyword keyword = new CommentKeyword("comment"); assertThat(keyword.hasName("$comment"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -50,17 +43,6 @@ void should_know_her_name() { @Test void should_be_printable() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$comment", Json.createValue("comment")).build()).printOn( - new HashMapMedia() - ), - hasEntry("$comment", "comment") - ); - } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "$comment", CommentKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); + assertThat(new CommentKeyword("comment").printOn(new HashMapMedia()), hasEntry("$comment", "comment")); } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java index 4dd22189..878f9d38 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java @@ -29,12 +29,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; +import java.util.Map; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +39,7 @@ class DefsKeywordTest { @Test void should_know_his_name() { - final Keyword defs = createKeywordFrom( - Json.createObjectBuilder().add("$defs", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword defs = new DefsKeyword(new NamedJsonSchemas(Map.of())); assertThat(defs.hasName("$defs"), is(true)); assertThat(defs.hasName("test"), is(false)); @@ -53,16 +48,8 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$defs", JsonValue.EMPTY_JSON_OBJECT).build()).printOn( - new HashMapMedia() - ), + new DefsKeyword(new NamedJsonSchemas(Map.of())).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$defs"), anEmptyMap()) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType(DefsKeyword.NAME, DefsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java index d909a1a9..5c708491 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java @@ -28,13 +28,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,37 +38,27 @@ class DynamicRefKeywordTest { @Test void should_create_keyword_with_name() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()).hasName("$dynamicRef"), - is(true) - ); - } - - @Test - void notFinischedYet() { - final Keyword keyword = createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()); + final Keyword keyword = new DynamicRefKeyword(URI.create("test")); assertThat(keyword.hasName("$dynamicRef"), is(true)); assertThat(keyword.hasName("$id"), is(false)); - - assertThat(keyword.asApplicator().applyTo(JsonValue.TRUE), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()).printOn( - new HashMapMedia() - ), + new DynamicRefKeyword(URI.create("test")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$dynamicRef"), is("test")) ); } - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType( - JsonProvider.provider(), - DynamicRefKeyword.NAME, - s -> new DynamicRefKeyword(URI.create(s)) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); + @Test + void notFinischedYet() { + final Keyword keyword = new DynamicRefKeyword(URI.create("test")); + + assertThat(keyword.hasName("$dynamicRef"), is(true)); + assertThat(keyword.hasName("$id"), is(false)); + + assertThat(keyword.asApplicator().applyTo(JsonValue.TRUE), is(true)); } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java index 0e23f507..3d9cb8fc 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java @@ -28,12 +28,7 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,7 +37,7 @@ class IdKeywordTest { @Test void should_know_his_name() { - final Keyword id = createKeywordFrom(Json.createObjectBuilder().add("$id", Json.createValue("/test")).build()); + final Keyword id = new IdKeyword(URI.create("/test")); assertThat(id.hasName("$id"), is(true)); assertThat(id.hasName("test"), is(false)); @@ -51,13 +46,7 @@ void should_know_his_name() { @Test void should_retun_his_uri() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$id", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ) - .asIdentifier() - .asUri(), + new IdKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).asIdentifier().asUri(), is(URI.create("https://json-schema.org/draft/2020-12/schema")) ); } @@ -65,18 +54,8 @@ void should_retun_his_uri() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$id", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ).printOn(new HashMapMedia()), + new IdKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$id"), is("https://json-schema.org/draft/2020-12/schema")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "$id", s -> new IdKeyword(URI.create(s))).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java index 489e2f0c..a1b5be96 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java @@ -26,52 +26,41 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; +import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; class RefKeywordTest { - @Test - void should_be_not_createable_from_non_string() { - final RefKeywordType keywordType = new RefKeywordType(JsonProvider.provider()); - final JsonSchema schema = new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$ref", JsonValue.TRUE).build()); - assertThrows(IllegalArgumentException.class, () -> keywordType.createKeyword(schema)); - } - @Test void should_know_his_name() { - final Keyword ref = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()) + final Keyword ref = new RefKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), + URI.create("#") ); - assertThat(ref.hasName("$ref"), is(true)); assertThat(ref.hasName("test"), is(false)); } @Test void should_use_local_referenced_schema_for_validation() { - final Keyword keyword = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$defs", - Json.createObjectBuilder() - .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) - ) - .add("$ref", Json.createValue("#/$defs/positiveInteger")) - .build() - ) + final Keyword keyword = new RefKeyword( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "$defs", + Json.createObjectBuilder() + .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) + ) + .add("$ref", Json.createValue("#/$defs/positiveInteger")) + .build() + ), + URI.create("#/$defs/positiveInteger") ); assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); @@ -80,32 +69,30 @@ void should_use_local_referenced_schema_for_validation() { @Test void should_use_remote_referenced_schema_for_validation() { - final Keyword keyword = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$defs", - Json.createObjectBuilder() - .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) - ) - .add("$ref", Json.createValue("#/$defs/positiveInteger")) - .build() - ) + final Keyword keyword = new RefKeyword( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "$defs", + Json.createObjectBuilder() + .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) + ) + .add("$ref", Json.createValue("#/$defs/positiveInteger")) + .build() + ), + URI.create("#/$defs/positiveInteger") ); - assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); + assertThat(keyword.asApplicator().applyTo(Json.createValue("invalid")), is(false)); } @Test void should_be_printable() { assertThat( - new RefKeywordType(JsonProvider.provider()) - .createKeyword( - new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()) - ) - .printOn(new HashMapMedia()), + new RefKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), + URI.create("#") + ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$ref"), is("#")) ); } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java index ab7ed341..5750e79d 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java @@ -25,8 +25,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import jakarta.json.Json; +import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import org.junit.jupiter.api.Test; @@ -40,4 +48,24 @@ void should_know_the_keywords_name() { assertThat(refKeywordType.hasName("ref"), is(false)); assertThat(refKeywordType.hasName("test"), is(false)); } + + @Test + void should_be_not_createable_from_non_string() { + final RefKeywordType keywordType = new RefKeywordType(JsonProvider.provider()); + final JsonSchema schema = new DefaultJsonSchemaFactory() + .create(Json.createObjectBuilder().add("$ref", JsonValue.TRUE).build()); + assertThrows(IllegalArgumentException.class, () -> keywordType.createKeyword(schema)); + } + + @Test + void should_create_refkeyword() { + assertThat( + new RefKeywordType(JsonProvider.provider()).createKeyword( + JsonSchemas.load( + Json.createObjectBuilder().add("$ref", Json.createValue("#/$defs/positiveInteger")).build() + ) + ), + is(not(nullValue())) + ); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java index e2ff6c7f..cc3eb4b5 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java @@ -28,12 +28,7 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +37,7 @@ class SchemaKeywordTest { @Test void should_know_his_name() { - final Keyword schema = createKeywordFrom( - Json.createObjectBuilder().add("$schema", Json.createValue("/test")).build() - ); + final Keyword schema = new SchemaKeyword(URI.create("/test")); assertThat(schema.hasName("$schema"), is(true)); assertThat(schema.hasName("test"), is(false)); @@ -53,13 +46,7 @@ void should_know_his_name() { @Test void should_retun_his_uri() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$schema", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ) - .asIdentifier() - .asUri(), + new SchemaKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).asIdentifier().asUri(), is(URI.create("https://json-schema.org/draft/2020-12/schema")) ); } @@ -67,20 +54,8 @@ void should_retun_his_uri() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$schema", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ).printOn(new HashMapMedia()), + new SchemaKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$schema"), is("https://json-schema.org/draft/2020-12/schema")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType( - JsonProvider.provider(), - "$schema", - s -> new SchemaKeyword(URI.create(s)) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java index 3ee97e25..5e7dbac9 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java @@ -30,10 +30,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; import jakarta.json.Json; import jakarta.json.JsonValue; import java.net.URI; @@ -44,11 +42,7 @@ class VocabularyKeywordTypeTest { @Test void should_created_keyword_should_know_his_name() { - final Keyword vocabulary = new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$vocabulary", JsonValue.EMPTY_JSON_OBJECT).build()) - ); + final Keyword vocabulary = new VocabularyKeyword(JsonValue.EMPTY_JSON_OBJECT); assertThat(vocabulary.hasName("$vocabulary"), is(true)); assertThat(vocabulary.hasName("$id"), is(false)); @@ -57,20 +51,13 @@ void should_created_keyword_should_know_his_name() { @Test void should_create_definitions() { assertThat( - ((VocabularyDefinitions) new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$vocabulary", - Json.createObjectBuilder() - .add("https://json-schema.org/draft/2020-12/vocab/core", true) - .add("http://openapi.org/test", false) - ) - .build() - ) - )).definitions() + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build() + ) + .definitions() .toList(), containsInAnyOrder( new VocabularyDefinition(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true), @@ -82,21 +69,12 @@ void should_create_definitions() { @Test void should_be_printable() { assertThat( - new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$vocabulary", - Json.createObjectBuilder() - .add("https://json-schema.org/draft/2020-12/vocab/core", true) - .add("http://openapi.org/test", false) - ) - .build() - ) - ) - .printOn(new HashMapMedia()), + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build() + ).printOn(new HashMapMedia()), (Matcher) hasEntry( is("$vocabulary"), allOf( diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java index fe9dd85f..d9822afd 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java @@ -23,8 +23,9 @@ */ package io.github.sebastiantoepfer.jsonschema.vocabulary.spi; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import java.util.stream.Stream; -public interface VocabularyDefinitions { +public interface VocabularyDefinitions extends Keyword { Stream definitions(); } From 1563195548efd13ee269095d9a3fd528dc6bd047 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:47:32 +0200 Subject: [PATCH 19/22] remove usage of record for VocabularyDefinition VocabularyDefinition is a interface now, the default implemention move into LazyVocabularyDefinition which no longer use the serviceloader directly. the vocabulary avaivable for a definitions will provided via a supplier. ServiceLoaderLazyVocabulariesSupplier can be use to use a ServiceLoader to find a requested vocabulary. VocabularyDefinition doesn't provide a method to retrieve the id instead it defines a method to ask if it has a given id (hasId("") instead of id().equals("")). --- .../jsonschema/core/Keywords.java | 4 +- .../applicator/ApplicatorVocabulary.java | 4 +- .../core/vocab/content/ContentVocabulary.java | 4 +- .../core/vocab/core/CoreVocabulary.java | 4 +- .../core/vocab/core/VocabularyKeyword.java | 11 +- .../format/FormatAnnotationVocabulary.java | 4 +- .../core/vocab/meta/MetaDataVocabulary.java | 4 +- .../unevaluated/UnevaluatedVocabulary.java | 4 +- .../validation/ValidationVocabulary.java | 4 +- .../jsonschema/core/KeywordsTest.java | 39 ++++++- ...peTest.java => VocabularyKeywordTest.java} | 40 ++++++- vocabulary-spi/pom.xml | 5 + .../spi/LazyVocabularyDefinition.java | 102 ++++++++++++++++++ ...ultVocabulary.java => ListVocabulary.java} | 6 +- ...ServiceLoaderLazyVocabulariesSupplier.java | 36 +++++++ .../vocabulary/spi/VocabularyDefinition.java | 20 ++-- ...java => LazyVocabularyDefinitionTest.java} | 39 ++++++- ...ularyTest.java => ListVocabularyTest.java} | 13 ++- 18 files changed, 292 insertions(+), 51 deletions(-) rename core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/{VocabularyKeywordTypeTest.java => VocabularyKeywordTest.java} (68%) create mode 100644 vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java rename vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/{DefaultVocabulary.java => ListVocabulary.java} (89%) create mode 100644 vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java rename vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/{VocabularyDefinitionTest.java => LazyVocabularyDefinitionTest.java} (60%) rename vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/{DefaultVocabularyTest.java => ListVocabularyTest.java} (86%) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java index a324741d..df243365 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java @@ -74,8 +74,8 @@ public Keywords(final Collection vocabDefs) { if ( vocabDefs .stream() - .filter(vocabDef -> MANDANTORY_VOCABS.containsKey(vocabDef.id())) - .anyMatch(not(VocabularyDefinition::required)) + .filter(vocabDef -> MANDANTORY_VOCABS.keySet().stream().anyMatch(vocabDef::hasid)) + .anyMatch(not(VocabularyDefinition::isRequired)) ) { throw new IllegalArgumentException("can not be created without core vocabulary is requiered!"); } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 5c65bb01..75384f96 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -33,7 +33,7 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.Json; import jakarta.json.JsonValue; import java.net.URI; @@ -52,7 +52,7 @@ public final class ApplicatorVocabulary implements Vocabulary { private final Vocabulary vocab; public ApplicatorVocabulary() { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/applicator"), new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java index 2948db29..ef6e04e5 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class ContentVocabulary implements Vocabulary { private final Vocabulary vocab; public ContentVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/content")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/content")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java index 26673186..0232c6fb 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java @@ -27,7 +27,7 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; import java.util.Optional; @@ -37,7 +37,7 @@ public final class CoreVocabulary implements Vocabulary { private final Vocabulary vocab; public CoreVocabulary(final JsonProvider jsonContext) { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/core"), new StringKeywordType(jsonContext, SchemaKeyword.NAME, value -> new SchemaKeyword(URI.create(value))), new StringKeywordType(jsonContext, IdKeyword.NAME, value -> new IdKeyword(URI.create(value))), diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java index 7dbb20ce..a2e62341 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java @@ -25,6 +25,8 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularyDefinition; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ServiceLoaderLazyVocabulariesSupplier; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; import jakarta.json.JsonObject; @@ -81,6 +83,13 @@ public Stream definitions() { return vocabularies .entrySet() .stream() - .map(entry -> new VocabularyDefinition(URI.create(entry.getKey()), entry.getValue() == JsonValue.TRUE)); + .map( + entry -> + new LazyVocabularyDefinition( + URI.create(entry.getKey()), + entry.getValue() == JsonValue.TRUE, + new ServiceLoaderLazyVocabulariesSupplier() + ) + ); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java index 4b55ac37..1f918bdd 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class FormatAnnotationVocabulary implements Vocabulary { private final Vocabulary vocab; public FormatAnnotationVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/format-annotation")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/format-annotation")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java index aed1b3e1..b642fbee 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class MetaDataVocabulary implements Vocabulary { private final Vocabulary vocab; public MetaDataVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/meta-data")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/meta-data")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java index e868460d..6ae14692 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class UnevaluatedVocabulary implements Vocabulary { private final Vocabulary vocab; public UnevaluatedVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/unevaluated")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/unevaluated")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java index 86e69b80..9e4bd0f9 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java @@ -35,7 +35,7 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; import java.util.List; @@ -46,7 +46,7 @@ public final class ValidationVocabulary implements Vocabulary { private final Vocabulary vocab; public ValidationVocabulary(final JsonProvider jsonContext) { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/validation"), new TypeKeywordType(), new AnyKeywordType(jsonContext, ConstKeyword.NAME, ConstKeyword::new), diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java index 0f42ad66..19c40788 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java @@ -29,12 +29,15 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertThrows; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import jakarta.json.spi.JsonProvider; import java.net.URI; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.junit.jupiter.api.Test; class KeywordsTest { @@ -42,7 +45,7 @@ class KeywordsTest { @Test void should_not_be_createbale_without_mandantory_core_vocabulary() { final Collection vocabDefs = List.of( - new VocabularyDefinition(new CoreVocabulary(JsonProvider.provider()).id(), false) + new TestVocabularyDefinition(new CoreVocabulary(JsonProvider.provider()).id(), false) ); assertThrows(IllegalArgumentException.class, () -> new Keywords(vocabDefs)); } @@ -50,7 +53,7 @@ void should_not_be_createbale_without_mandantory_core_vocabulary() { @Test void should_not_be_createbale_without_mandantory_base_vocabulary() { final Collection vocabDefs = List.of( - new VocabularyDefinition(new BasicVocabulary().id(), false) + new TestVocabularyDefinition(new BasicVocabulary().id(), false) ); assertThrows(IllegalArgumentException.class, () -> new Keywords(vocabDefs)); } @@ -58,8 +61,38 @@ void should_not_be_createbale_without_mandantory_base_vocabulary() { @Test void should_be_createbale_with_optional_vocabularies() { assertThat( - new Keywords(List.of(new VocabularyDefinition(URI.create("http://optinal"), false))), + new Keywords(List.of(new TestVocabularyDefinition(URI.create("http://optinal"), false))), is(not(nullValue())) ); } + + private static final class TestVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final boolean required; + + public TestVocabularyDefinition(final URI id) { + this(id, false); + } + + public TestVocabularyDefinition(final URI id, final boolean required) { + this.id = id; + this.required = required; + } + + @Override + public Optional findVocabulary() { + return Optional.empty(); + } + + @Override + public boolean hasid(URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return required; + } + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java similarity index 68% rename from core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java rename to core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java index 5e7dbac9..c12e2907 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java @@ -26,6 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; @@ -35,10 +36,13 @@ import jakarta.json.Json; import jakarta.json.JsonValue; import java.net.URI; +import java.util.Objects; +import org.hamcrest.Description; import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.Test; -class VocabularyKeywordTypeTest { +class VocabularyKeywordTest { @Test void should_created_keyword_should_know_his_name() { @@ -60,8 +64,8 @@ void should_create_definitions() { .definitions() .toList(), containsInAnyOrder( - new VocabularyDefinition(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true), - new VocabularyDefinition(URI.create("http://openapi.org/test"), false) + new VocabularyDefinitionMatcher(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true), + new VocabularyDefinitionMatcher(URI.create("http://openapi.org/test"), false) ) ); } @@ -84,4 +88,34 @@ void should_be_printable() { ) ); } + + private static class VocabularyDefinitionMatcher extends TypeSafeMatcher { + + private final URI id; + private final Matcher required; + + public VocabularyDefinitionMatcher(final URI id) { + this(id, null); + } + + public VocabularyDefinitionMatcher(final URI id, final Boolean required) { + super(VocabularyDefinition.class); + this.id = Objects.requireNonNull(id); + this.required = required == null ? either(is(true)).or(is(false)) : is(required); + } + + @Override + protected boolean matchesSafely(final VocabularyDefinition item) { + return item.hasid(id) && required.matches(item.isRequired()); + } + + @Override + public void describeTo(final Description description) { + description + .appendText("Vocabulary definition with id: ") + .appendValue(id) + .appendText(" and required: ") + .appendDescriptionOf(required); + } + } } diff --git a/vocabulary-spi/pom.xml b/vocabulary-spi/pom.xml index 47ff506f..e36b01b3 100644 --- a/vocabulary-spi/pom.xml +++ b/vocabulary-spi/pom.xml @@ -42,6 +42,11 @@ hamcrest-optional test
+ + nl.jqno.equalsverifier + equalsverifier + test + jakarta.json diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java new file mode 100644 index 00000000..de1a4e2a --- /dev/null +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java @@ -0,0 +1,102 @@ +/* + * 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.vocabulary.spi; + +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import java.net.URI; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class LazyVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final boolean required; + private final Supplier> lazyVocabularies; + + public LazyVocabularyDefinition( + final URI id, + final boolean required, + final Supplier> lazyVocabularies + ) { + this.id = Objects.requireNonNull(id); + this.required = required; + this.lazyVocabularies = Objects.requireNonNull(lazyVocabularies); + } + + @Override + public Optional findVocabulary() { + final Optional result = lazyVocabularies + .get() + .map(loader -> loader.loadVocabularyWithId(id)) + .flatMap(Optional::stream) + .findFirst(); + if (result.isEmpty() && isRequired()) { + throw new IllegalStateException("can not find required vocabulary: " + id); + } + return result; + } + + @Override + public boolean hasid(final URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return required; + } + + @Override + public String toString() { + return "LazyVocabularyDefinition{" + "id=" + id + ", required=" + required + '}'; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 59 * hash + Objects.hashCode(this.id); + hash = 59 * hash + (this.required ? 1 : 0); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LazyVocabularyDefinition other = (LazyVocabularyDefinition) obj; + if (this.required != other.required) { + return false; + } + return Objects.equals(this.id, other.id); + } +} diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java similarity index 89% rename from vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java rename to vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java index 2d551ff6..4228a577 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java @@ -32,16 +32,16 @@ import java.util.Objects; import java.util.Optional; -public final class DefaultVocabulary implements Vocabulary { +public final class ListVocabulary implements Vocabulary { private final URI id; private final List keywords; - public DefaultVocabulary(final URI id, final KeywordType... keywortds) { + public ListVocabulary(final URI id, final KeywordType... keywortds) { this(id, Arrays.asList(keywortds)); } - public DefaultVocabulary(final URI id, final Collection keywords) { + public ListVocabulary(final URI id, final Collection keywords) { this.id = Objects.requireNonNull(id); this.keywords = List.copyOf(keywords); } diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java new file mode 100644 index 00000000..93774c89 --- /dev/null +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java @@ -0,0 +1,36 @@ +/* + * 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.vocabulary.spi; + +import java.util.ServiceLoader; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class ServiceLoaderLazyVocabulariesSupplier implements Supplier> { + + @Override + public Stream get() { + return ServiceLoader.load(LazyVocabularies.class).stream().map(ServiceLoader.Provider::get); + } +} diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java index a14c7d61..d53c3574 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java @@ -26,19 +26,11 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import java.net.URI; import java.util.Optional; -import java.util.ServiceLoader; -public record VocabularyDefinition(URI id, boolean required) { - public Optional findVocabulary() { - final Optional result = ServiceLoader.load(LazyVocabularies.class) - .stream() - .map(ServiceLoader.Provider::get) - .map(loader -> loader.loadVocabularyWithId(id)) - .flatMap(Optional::stream) - .findFirst(); - if (result.isEmpty() && required) { - throw new IllegalStateException("can not find required vocabulary: " + id); - } - return result; - } +public interface VocabularyDefinition { + Optional findVocabulary(); + + boolean hasid(URI id); + + boolean isRequired(); } diff --git a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java similarity index 60% rename from vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java rename to vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java index dd582c88..72d6ab4d 100644 --- a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java +++ b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java @@ -26,24 +26,41 @@ import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty; import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.jsonschema.Vocabulary; import java.net.URI; +import java.util.stream.Stream; +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; import org.junit.jupiter.api.Test; -class VocabularyDefinitionTest { +class LazyVocabularyDefinitionTest { + + @Test + void verifyEqualsContract() { + EqualsVerifier.forClass(LazyVocabularyDefinition.class).suppress(Warning.ALL_FIELDS_SHOULD_BE_USED).verify(); + } @Test void should_throw_illegal_state_if_a_required_vocabulary_can_not_be_loaded() { - final VocabularyDefinition vocabDef = new VocabularyDefinition(URI.create("https://invalid"), true); + final VocabularyDefinition vocabDef = new LazyVocabularyDefinition( + URI.create("https://invalid"), + true, + () -> Stream.empty() + ); assertThrows(IllegalStateException.class, () -> vocabDef.findVocabulary()); } @Test void should_find_mandatory_core_vocabulary() { assertThat( - new VocabularyDefinition(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true) + new LazyVocabularyDefinition( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + true, + new ServiceLoaderLazyVocabulariesSupplier() + ) .findVocabulary() .map(Vocabulary::id), isPresentAndIs(URI.create("https://json-schema.org/draft/2020-12/vocab/core")) @@ -53,8 +70,22 @@ void should_find_mandatory_core_vocabulary() { @Test void should_retrun_empty_for_optional_vocabulary_which_can_not_be_loaded() { assertThat( - new VocabularyDefinition(URI.create("https://invalid"), false).findVocabulary().map(Vocabulary::id), + new LazyVocabularyDefinition(URI.create("https://invalid"), false, () -> Stream.empty()) + .findVocabulary() + .map(Vocabulary::id), isEmpty() ); } + + @Test + void should_know_this_id() { + final LazyVocabularyDefinition vocbDef = new LazyVocabularyDefinition( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + false, + () -> Stream.empty() + ); + + assertThat(vocbDef.hasid(URI.create("https://json-schema.org/draft/2020-12/vocab/core")), is(true)); + assertThat(vocbDef.hasid(URI.create("https://invalid")), is(false)); + } } diff --git a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java similarity index 86% rename from vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java rename to vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java index bcc681e4..96f08877 100644 --- a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java +++ b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java @@ -34,12 +34,12 @@ import java.net.URI; import org.junit.jupiter.api.Test; -class DefaultVocabularyTest { +class ListVocabularyTest { @Test void should_return_the_id() { assertThat( - new DefaultVocabulary(URI.create("http::/localhots/vocab")).id(), + new ListVocabulary(URI.create("http::/localhots/vocab")).id(), is(URI.create("http::/localhots/vocab")) ); } @@ -47,10 +47,9 @@ void should_return_the_id() { @Test void should_return_empty_if_requested_keyword_is_unknown() { assertThat( - new DefaultVocabulary( - URI.create("http://localhost"), - new SimpleTestKeywordType("name") - ).findKeywordTypeByName("test"), + new ListVocabulary(URI.create("http://localhost"), new SimpleTestKeywordType("name")).findKeywordTypeByName( + "test" + ), isEmpty() ); } @@ -59,7 +58,7 @@ void should_return_empty_if_requested_keyword_is_unknown() { void should_return_requested_keyword_if_is_it_known() { final KeywordType testKeywordType = new SimpleTestKeywordType("test"); assertThat( - new DefaultVocabulary(URI.create("http://localhost"), testKeywordType).findKeywordTypeByName("test"), + new ListVocabulary(URI.create("http://localhost"), testKeywordType).findKeywordTypeByName("test"), isPresentAndIs(testKeywordType) ); } From 2d5313609368b26ea1b15e2065a64f7bf9b3dc35 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Sun, 9 Jun 2024 14:36:13 +0200 Subject: [PATCH 20/22] do not longer lazyload officalvocabs --- .../core/vocab/core/VocabularyKeyword.java | 62 +++++++++++---- .../vocab/core/VocabularyKeywordType.java | 9 ++- .../vocab/core/VocabularyKeywordTest.java | 75 ++++++++++++++++++- 3 files changed, 127 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java index a2e62341..1bff8b10 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java @@ -25,8 +25,10 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.OfficialVocabularies; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularyDefinition; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ServiceLoaderLazyVocabulariesSupplier; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; import jakarta.json.JsonObject; @@ -34,7 +36,10 @@ import java.net.URI; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Stream; /** @@ -53,13 +58,18 @@ final class VocabularyKeyword implements VocabularyDefinitions { static final String NAME = "$vocabulary"; private final JsonObject vocabularies; + private final Supplier> lazyVocabulariesSupplier; - VocabularyKeyword(final JsonValue vocabularies) { - this(vocabularies.asJsonObject()); + VocabularyKeyword(final JsonValue vocabularies, final Supplier> lazyVocabulariesSupplier) { + this(vocabularies.asJsonObject(), lazyVocabulariesSupplier); } - VocabularyKeyword(final JsonObject vocabularies) { + VocabularyKeyword( + final JsonObject vocabularies, + final Supplier> lazyVocabulariesSupplier + ) { this.vocabularies = Objects.requireNonNull(vocabularies); + this.lazyVocabulariesSupplier = Objects.requireNonNull(lazyVocabulariesSupplier); } @Override @@ -80,16 +90,38 @@ public Collection categories() { @Override public Stream definitions() { - return vocabularies - .entrySet() - .stream() - .map( - entry -> - new LazyVocabularyDefinition( - URI.create(entry.getKey()), - entry.getValue() == JsonValue.TRUE, - new ServiceLoaderLazyVocabulariesSupplier() - ) - ); + return vocabularies.entrySet().stream().map(LazyNonOfficalVocabularyDefinition::new); + } + + private final class LazyNonOfficalVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final JsonValue required; + + public LazyNonOfficalVocabularyDefinition(final Map.Entry property) { + this(URI.create(property.getKey()), property.getValue()); + } + + public LazyNonOfficalVocabularyDefinition(final URI id, final JsonValue required) { + this.id = Objects.requireNonNull(id); + this.required = required; + } + + @Override + public Optional findVocabulary() { + return new OfficialVocabularies() + .loadVocabularyWithId(id) + .or(() -> new LazyVocabularyDefinition(id, isRequired(), lazyVocabulariesSupplier).findVocabulary()); + } + + @Override + public boolean hasid(final URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return JsonValue.TRUE.equals(required); + } } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java index e099babb..78cff548 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java @@ -26,6 +26,7 @@ import io.github.sebastiantoepfer.jsonschema.InstanceType; import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ServiceLoaderLazyVocabulariesSupplier; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; import jakarta.json.JsonValue; @@ -34,6 +35,12 @@ */ public final class VocabularyKeywordType implements KeywordType { + private final ServiceLoaderLazyVocabulariesSupplier lazyVocabulariesSupplier; + + public VocabularyKeywordType() { + this.lazyVocabulariesSupplier = new ServiceLoaderLazyVocabulariesSupplier(); + } + @Override public String name() { return VocabularyKeyword.NAME; @@ -44,7 +51,7 @@ public VocabularyDefinitions createKeyword(final JsonSchema schema) { final JsonValue value = schema.asJsonObject().get((name())); final VocabularyKeyword result; if (InstanceType.OBJECT.isInstance(value)) { - result = new VocabularyKeyword(value); + result = new VocabularyKeyword(value, lazyVocabulariesSupplier); } else { throw new IllegalArgumentException( "must be an object! " + diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java index c12e2907..e0356c83 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java @@ -31,12 +31,18 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import jakarta.json.Json; import jakarta.json.JsonValue; import java.net.URI; +import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; @@ -46,7 +52,7 @@ class VocabularyKeywordTest { @Test void should_created_keyword_should_know_his_name() { - final Keyword vocabulary = new VocabularyKeyword(JsonValue.EMPTY_JSON_OBJECT); + final Keyword vocabulary = new VocabularyKeyword(JsonValue.EMPTY_JSON_OBJECT, Stream::empty); assertThat(vocabulary.hasName("$vocabulary"), is(true)); assertThat(vocabulary.hasName("$id"), is(false)); @@ -59,7 +65,8 @@ void should_create_definitions() { Json.createObjectBuilder() .add("https://json-schema.org/draft/2020-12/vocab/core", true) .add("http://openapi.org/test", false) - .build() + .build(), + Stream::empty ) .definitions() .toList(), @@ -77,7 +84,8 @@ void should_be_printable() { Json.createObjectBuilder() .add("https://json-schema.org/draft/2020-12/vocab/core", true) .add("http://openapi.org/test", false) - .build() + .build(), + Stream::empty ).printOn(new HashMapMedia()), (Matcher) hasEntry( is("$vocabulary"), @@ -89,6 +97,67 @@ void should_be_printable() { ); } + @Test + void should_not_lazy_load_offical_vocabs() { + //security and peformance. it should not be possible to override keywords define by this module! + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", true).build(), + Stream::empty + ) + .definitions() + .map(VocabularyDefinition::findVocabulary) + .flatMap(Optional::stream) + .map(Vocabulary::id) + .toList(), + containsInAnyOrder(URI.create("https://json-schema.org/draft/2020-12/vocab/core")) + ); + } + + @Test + void should_provide_vocab_which_know_they_id() { + final VocabularyDefinition vocabDef = new VocabularyKeyword( + Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", true).build(), + Stream::empty + ) + .definitions() + .findFirst() + .orElseThrow(); + + assertThat(vocabDef.hasid(URI.create("https://json-schema.org/draft/2020-12/vocab/core")), is(true)); + assertThat(vocabDef.hasid(URI.create("http://openapi.org/test")), is(false)); + } + + @Test + void should_lazy_load_non_offical_vocabs() { + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build(), + () -> + Stream.of( + new LazyVocabularies() { + @Override + public Optional loadVocabularyWithId(final URI id) { + return Optional.of(new ListVocabulary(id, List.of())); + } + } + ) + ) + .definitions() + .map(VocabularyDefinition::findVocabulary) + .flatMap(Optional::stream) + .map(Vocabulary::id) + .toList(), + containsInAnyOrder( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + URI.create("http://openapi.org/test") + ) + ); + } + private static class VocabularyDefinitionMatcher extends TypeSafeMatcher { private final URI id; From 113096b7f3cd87e44c20acb5d4fcbb53edac6aaf Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:35:15 +0200 Subject: [PATCH 21/22] improve refkeyword to improve test with remote schema --- .../core/vocab/core/RefKeyword.java | 21 ++++--- .../core/vocab/core/RefKeywordType.java | 4 +- .../core/vocab/core/SchemaRegistry.java | 60 +++++++++++++++++++ .../core/vocab/core/RefKeywordTest.java | 43 +++++++++---- .../core/vocab/core/SchemaRegistryTest.java | 42 +++++++++++++ 5 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java 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 c337f5f4..de709526 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 @@ -29,7 +29,6 @@ import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; import jakarta.json.Json; import jakarta.json.JsonPointer; -import jakarta.json.JsonReader; import jakarta.json.JsonStructure; import jakarta.json.JsonValue; import java.io.IOException; @@ -52,10 +51,12 @@ final class RefKeyword implements Applicator { static final String NAME = "$ref"; private final JsonSchema schema; private final URI uri; + private final SchemaRegistry schemaRegistry; - public RefKeyword(final JsonSchema schema, final URI uri) { + public RefKeyword(final JsonSchema schema, final URI uri, final SchemaRegistry schemaRegistry) { this.schema = Objects.requireNonNull(schema); this.uri = Objects.requireNonNull(uri); + this.schemaRegistry = Objects.requireNonNull(schemaRegistry); } @Override @@ -74,12 +75,12 @@ public boolean hasName(final String name) { } private JsonSchema retrieveJsonSchema() { - final JsonValue json; + final JsonSchema json; try { if (isRemote()) { - json = retrieveValueFromRemoteLocation(); + json = retrieveSchemaFromRegistry(); } else { - json = retrieveValueFromLocalSchema(); + json = retrieveSchemaFromLocalSchema(); } return JsonSchemas.load(json); } catch (IOException ex) { @@ -87,10 +88,10 @@ private JsonSchema retrieveJsonSchema() { } } - private JsonValue retrieveValueFromLocalSchema() throws IOException { + private JsonSchema retrieveSchemaFromLocalSchema() throws IOException { final JsonPointer pointer = createPointer(); if (pointer.containsValue(searchAnchor())) { - return pointer.getValue(searchAnchor()); + return JsonSchemas.load(pointer.getValue(searchAnchor())); } else { throw new IOException("can not find referenced value."); } @@ -111,10 +112,8 @@ private JsonPointer createPointer() { return pointer; } - private JsonValue retrieveValueFromRemoteLocation() throws IOException { - try (final JsonReader reader = Json.createReader(uri.toURL().openStream())) { - return reader.readValue(); - } + private JsonSchema retrieveSchemaFromRegistry() throws IOException { + return schemaRegistry.schemaForUrl(uri); } private boolean isRemote() { diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java index dcff9353..93dd6b77 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java @@ -34,9 +34,11 @@ final class RefKeywordType implements KeywordType { private final JsonProvider jsonContext; + private final SchemaRegistry schemaRegistry; public RefKeywordType(final JsonProvider jsonContext) { this.jsonContext = Objects.requireNonNull(jsonContext); + this.schemaRegistry = new SchemaRegistry.RemoteSchemaRegistry(); } @Override @@ -49,7 +51,7 @@ public Keyword createKeyword(final JsonSchema schema) { return new StringKeywordType( jsonContext, RefKeyword.NAME, - s -> new RefKeyword(schema, URI.create(s)) + s -> new RefKeyword(schema, URI.create(s), schemaRegistry) ).createKeyword(schema); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java new file mode 100644 index 00000000..1fb4ffe9 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java @@ -0,0 +1,60 @@ +/* + * 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.vocab.core; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import jakarta.json.Json; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; +import java.io.IOException; +import java.net.URI; + +interface SchemaRegistry { + JsonSchema schemaForUrl(URI uri) throws IOException; + + class DefaultSchemaRegistry implements SchemaRegistry { + + private JsonSchema schema; + + public DefaultSchemaRegistry() { + schema = JsonSchemas.load(JsonValue.FALSE); + } + + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + return schema; + } + } + + class RemoteSchemaRegistry implements SchemaRegistry { + + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + try (JsonReader reader = Json.createReader(uri.toURL().openStream())) { + return JsonSchemas.load(reader.readValue()); + } + } + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java index a1b5be96..0a1e9baf 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java @@ -28,9 +28,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; +import java.io.IOException; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -41,7 +43,8 @@ class RefKeywordTest { void should_know_his_name() { final Keyword ref = new RefKeyword( JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), - URI.create("#") + URI.create("#"), + new SchemaRegistry.DefaultSchemaRegistry() ); assertThat(ref.hasName("$ref"), is(true)); assertThat(ref.hasName("test"), is(false)); @@ -60,7 +63,8 @@ void should_use_local_referenced_schema_for_validation() { .add("$ref", Json.createValue("#/$defs/positiveInteger")) .build() ), - URI.create("#/$defs/positiveInteger") + URI.create("#/$defs/positiveInteger"), + new SchemaRegistry.DefaultSchemaRegistry() ); assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); @@ -71,19 +75,31 @@ void should_use_local_referenced_schema_for_validation() { void should_use_remote_referenced_schema_for_validation() { final Keyword keyword = new RefKeyword( JsonSchemas.load( - Json.createObjectBuilder() - .add( - "$defs", - Json.createObjectBuilder() - .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) - ) - .add("$ref", Json.createValue("#/$defs/positiveInteger")) - .build() + Json.createObjectBuilder().add("$ref", Json.createValue("https://schema.org/byMonth")).build() ), - URI.create("#/$defs/positiveInteger") + URI.create("https://schema.org/byMonth"), + new SchemaRegistry() { + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + if (uri.equals(URI.create("https://schema.org/byMonth"))) { + //hen egg issue -> we need more than core :( + return JsonSchemas.load( + Json.createObjectBuilder() + .add("type", "integer") + .add("minimum", 1) + .add("maximum", 12) + .build() + ); + } else { + throw new IOException("can not retrieve remote schema!"); + } + } + } ); assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); - assertThat(keyword.asApplicator().applyTo(Json.createValue("invalid")), is(false)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(12L)), is(true)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(0L)), is(false)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(13L)), is(false)); } @Test @@ -91,7 +107,8 @@ void should_be_printable() { assertThat( new RefKeyword( JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), - URI.create("#") + URI.create("#"), + new SchemaRegistry.DefaultSchemaRegistry() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$ref"), is("#")) ); diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java new file mode 100644 index 00000000..9723eec0 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java @@ -0,0 +1,42 @@ +/* + * 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.vocab.core; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import jakarta.json.JsonValue; +import java.net.URI; +import org.junit.jupiter.api.Test; + +class SchemaRegistryTest { + + @Test + void should_return_false_schema() throws Exception { + assertThat( + new SchemaRegistry.DefaultSchemaRegistry().schemaForUrl(URI.create("test")).getValueType(), + is(JsonValue.ValueType.FALSE) + ); + } +} From f696547f9e79d0e1104ea9545568d5d56faeb85d Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Thu, 6 Jun 2024 20:17:08 +0200 Subject: [PATCH 22/22] refactor test for all keyword defined in validationvocab --- .../vocab/validation/ConstKeywordTest.java | 42 ++++----- .../DependentRequiredKeywordTest.java | 48 ++--------- .../vocab/validation/EnumKeywordTest.java | 29 ++----- .../ExclusiveMaximumKeywordTest.java | 36 ++------ .../ExclusiveMinimumKeywordTest.java | 36 ++------ .../validation/MaxContainsKeywordTest.java | 86 ++++--------------- .../vocab/validation/MaxItemsKeywordTest.java | 28 ++---- .../validation/MaxLengthKeywordTest.java | 32 ++----- .../validation/MaxPropertiesKeywordTest.java | 26 ++---- .../vocab/validation/MaximumKeywordTest.java | 40 ++------- .../validation/MinContainsKeywordTest.java | 86 ++++--------------- .../vocab/validation/MinItemsKeywordTest.java | 30 ++----- .../validation/MinLengthKeywordTest.java | 34 ++------ .../validation/MinPropertiesKeywordTest.java | 26 ++---- .../vocab/validation/MinimumKeywordTest.java | 43 ++-------- .../validation/MultipleOfKeywordTest.java | 36 ++------ .../vocab/validation/PatternKeywordTest.java | 39 ++------- .../vocab/validation/RequiredKeywordTest.java | 33 ++----- .../validation/UniqueItemsKeywordTest.java | 39 ++------- 19 files changed, 149 insertions(+), 620 deletions(-) diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java index 15edcad9..63d7f2e9 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java @@ -29,11 +29,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AnyKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import org.hamcrest.Matcher; @@ -43,9 +40,7 @@ class ConstKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("const", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword enumKeyword = new ConstKeyword(JsonProvider.provider(), JsonValue.EMPTY_JSON_ARRAY); assertThat(enumKeyword.hasName("const"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -54,7 +49,7 @@ void should_know_his_name() { @Test void should_be_valid_for_same_string_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue("hello")).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue("hello")) .asAssertion() .isValidFor(Json.createValue("hello")), is(true) @@ -64,7 +59,7 @@ void should_be_valid_for_same_string_value() { @Test void should_be_invalid_for_different_string_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue("hello")).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue("hello")) .asAssertion() .isValidFor(Json.createValue("world")), is(false) @@ -74,7 +69,7 @@ void should_be_invalid_for_different_string_value() { @Test void should_be_valid_for_same_number_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue(3.14159)).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(3.14159)) .asAssertion() .isValidFor(Json.createValue(3.14159)), is(true) @@ -84,7 +79,7 @@ void should_be_valid_for_same_number_value() { @Test void should_be_invalid_for_value_with_different_type() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue(3.14159)).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(3.14159)) .asAssertion() .isValidFor(Json.createValue("pi")), is(false) @@ -94,10 +89,9 @@ void should_be_invalid_for_value_with_different_type() { @Test void should_be_valid_for_exact_object_structure() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("const", Json.createObjectBuilder().add("name", "John Doe").add("age", 31)) - .build() + new ConstKeyword( + JsonProvider.provider(), + Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build()), @@ -108,10 +102,9 @@ void should_be_valid_for_exact_object_structure() { @Test void should_be_invalid_for_not_exact_object_structure() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("const", Json.createObjectBuilder().add("name", "John Doe").add("age", 31)) - .build() + new ConstKeyword( + JsonProvider.provider(), + Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "Robert").add("age", 31).build()), @@ -122,7 +115,7 @@ void should_be_invalid_for_not_exact_object_structure() { @Test void should_be_valid_for_decimal_without_scale_if_number_is_valid() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", 1).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(1)) .asAssertion() .isValidFor(Json.createValue(1.0)), is(true) @@ -132,16 +125,11 @@ void should_be_valid_for_decimal_without_scale_if_number_is_valid() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("const", Json.createArrayBuilder().add("TEST").add("VALID")).build() + new ConstKeyword( + JsonProvider.provider(), + Json.createArrayBuilder().add("TEST").add("VALID").build() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("const"), containsInAnyOrder("TEST", "VALID")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AnyKeywordType(JsonProvider.provider(), "const", ConstKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java index 00a0e529..0a361c80 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java @@ -30,8 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ObjectKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; @@ -62,9 +60,7 @@ void should_not_be_createable_with_object_contains_only_non_string_array_propert @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("dependentRequired", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword enumKeyword = new DependentRequiredKeyword(JsonValue.EMPTY_JSON_OBJECT); assertThat(enumKeyword.hasName("dependentRequired"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -73,13 +69,8 @@ void should_know_his_name() { @Test void should_be_valid_if_both_properties_available() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor( @@ -92,13 +83,8 @@ void should_be_valid_if_both_properties_available() { @Test void should_be_valid_if_required_properties_is_missing() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John").add("license", "XYZ123").build()), @@ -109,13 +95,8 @@ void should_be_valid_if_required_properties_is_missing() { @Test void should_be_valid_if_both_properties_are_missing() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John").build()), @@ -126,21 +107,10 @@ void should_be_valid_if_both_properties_are_missing() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("dependentRequired"), hasEntry(is("license"), contains("age"))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new ObjectKeywordType("dependentRequired", DependentRequiredKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java index cb7c6890..440da115 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java @@ -29,11 +29,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +39,7 @@ class EnumKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("enum", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword enumKeyword = new EnumKeyword(JsonValue.EMPTY_JSON_ARRAY); assertThat(enumKeyword.hasName("enum"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -53,9 +48,7 @@ void should_know_his_name() { @Test void should_valid_for_string_value_which_is_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ) + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()) .asAssertion() .isValidFor(Json.createValue("TEST")), is(true) @@ -65,9 +58,7 @@ void should_valid_for_string_value_which_is_in_array() { @Test void should_be_invalid_for_number_which_is_not_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ) + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()) .asAssertion() .isValidFor(Json.createValue(2)), is(false) @@ -77,9 +68,7 @@ void should_be_invalid_for_number_which_is_not_in_array() { @Test void should_be_valid_for_decimal_without_scale_if_number_is_valid() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add(1)).build()) - .asAssertion() - .isValidFor(Json.createValue(1.0)), + new EnumKeyword(Json.createArrayBuilder().add(1).build()).asAssertion().isValidFor(Json.createValue(1.0)), is(true) ); } @@ -87,16 +76,8 @@ void should_be_valid_for_decimal_without_scale_if_number_is_valid() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ).printOn(new HashMapMedia()), + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()).printOn(new HashMapMedia()), (Matcher) hasEntry(is("enum"), containsInAnyOrder("TEST", "VALID")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new ArrayKeywordType("enum", EnumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java index 9242c0a2..b1b51d47 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class ExclusiveMaximumKeywordTest { @Test void should_know_his_name() { - final Keyword maximum = createKeywordFrom( - Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(1)).build() - ); + final Keyword maximum = new ExclusiveMaximumKeyword(BigDecimal.valueOf(1)); assertThat(maximum.hasName("exclusiveMaximum"), is(true)); assertThat(maximum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(11)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(11)), is(false) ); } @@ -74,9 +64,7 @@ void should_be_invalid_for_greater_numbers() { @Test void should_be_invalid_for_equals_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(10)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(10)), is(false) ); } @@ -84,9 +72,7 @@ void should_be_invalid_for_equals_numbers() { @Test void shhould_be_valid_for_smaller_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(9)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(9)), is(true) ); } @@ -94,18 +80,8 @@ void shhould_be_valid_for_smaller_numbers() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()).printOn( - new HashMapMedia() - ), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("exclusiveMaximum"), is(BigDecimal.valueOf(10))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType( - JsonProvider.provider(), - "exclusiveMaximum", - ExclusiveMaximumKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java index c6ca0db8..d19d4185 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class ExclusiveMinimumKeywordTest { @Test void should_know_his_name() { - final Keyword minimum = createKeywordFrom( - Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(1)).build() - ); + final Keyword minimum = new ExclusiveMinimumKeyword(BigDecimal.valueOf(1)); assertThat(minimum.hasName("exclusiveMinimum"), is(true)); assertThat(minimum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_smaller_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(-1)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(-1)), is(false) ); } @@ -74,9 +64,7 @@ void should_be_invalid_for_smaller_numbers() { @Test void should_be_invalid_for_equals_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(0)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(0)), is(false) ); } @@ -84,9 +72,7 @@ void should_be_invalid_for_equals_numbers() { @Test void shhould_be_valid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(1)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(1)), is(true) ); } @@ -94,18 +80,8 @@ void shhould_be_valid_for_greater_numbers() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()).printOn( - new HashMapMedia() - ), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("exclusiveMinimum"), is(BigDecimal.valueOf(0))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType( - JsonProvider.provider(), - "exclusiveMinimum", - ExclusiveMinimumKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java index bff8d9b2..4b1c889b 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java @@ -28,16 +28,10 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import java.util.List; import org.hamcrest.Matcher; @@ -47,10 +41,7 @@ class MaxContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MaxContainsKeyword( - List.of(new StaticAnnotation("", JsonValue.NULL)), - BigInteger.ONE - ); + final Keyword enumKeyword = new MaxContainsKeyword(List.of(), BigInteger.ONE); assertThat(enumKeyword.hasName("maxContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); } @@ -58,7 +49,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()).printOn(new HashMapMedia()), + new MaxContainsKeyword(List.of(), BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxContains"), is(BigInteger.valueOf(2))) ); } @@ -66,12 +57,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(), BigInteger.valueOf(2)) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -81,11 +67,9 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).build()), @@ -96,11 +80,9 @@ void should_be_valid_if_contains_applies_to_exact_count() { @Test void should_be_valid_if_contains_applies_to_less_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(2).add(3).build()), @@ -111,11 +93,9 @@ void should_be_valid_if_contains_applies_to_less_items() { @Test void should_be_valid_for_empty_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", JsonValue.EMPTY_JSON_ARRAY)), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_ARRAY), @@ -126,11 +106,9 @@ void should_be_valid_for_empty_arrays() { @Test void should_be_invalid_if_contains_applies_to_more_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).add(3).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).add("baz").build()), @@ -141,12 +119,7 @@ void should_be_invalid_if_contains_applies_to_more_items() { @Test void should_be_valid_if_contains_applies_to_all_and_less_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").build()), is(true) @@ -156,12 +129,7 @@ void should_be_valid_if_contains_applies_to_all_and_less_items_in_array() { @Test void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").build()), is(true) @@ -171,28 +139,10 @@ void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() @Test void should_be_invalid_if_contains_applies_to_all_and_more_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectsKeywordType( - "maxContains", - List.of(new Affects("contains", new Affects.ReplaceKeyword())), - (a, s) -> - new IntegerKeywordType( - JsonProvider.provider(), - "maxContains", - value -> new MaxContainsKeyword(a, value) - ).createKeyword(s) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java index 182a3360..de931481 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MaxItemsKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("maxItems", Json.createValue(1)).build() - ); + final Keyword keyword = new MaxItemsKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("maxItems"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MaxItemsKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,7 +56,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_for_array_with_same_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -74,7 +66,7 @@ void should_be_valid_for_array_with_same_size() { @Test void should_be_valid_for_array_with_smaller_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).build()), is(true) @@ -84,7 +76,7 @@ void should_be_valid_for_array_with_smaller_size() { @Test void should_be_invalid_for_array_with_greather_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).add(3).build()), is(false) @@ -94,16 +86,8 @@ void should_be_invalid_for_array_with_greather_size() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MaxItemsKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxItems"), is(BigInteger.valueOf(2))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "maxItems", MaxItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java index 886a2ef8..b995e9bb 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MaxLengthKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom(Json.createObjectBuilder().add("maxLength", 1).build()); + final Keyword keyword = new MaxLengthKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("test"), is(false)); assertThat(keyword.hasName("maxLength"), is(true)); @@ -52,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_string_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -62,9 +56,7 @@ void should_be_valid_for_non_string_values() { @Test void should_be_invalid_for_longer_string() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("1234")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("1234")), is(false) ); } @@ -72,9 +64,7 @@ void should_be_invalid_for_longer_string() { @Test void should_be_valid_for_string_with_length_is_equal() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("12")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("12")), is(true) ); } @@ -82,9 +72,7 @@ void should_be_valid_for_string_with_length_is_equal() { @Test void should_ne_valid_for_string_with_is_shorter() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("1")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("1")), is(true) ); } @@ -92,16 +80,8 @@ void should_ne_valid_for_string_with_is_shorter() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MaxLengthKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxLength"), is(BigInteger.valueOf(2L))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "maxLength", MaxLengthKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java index b82c2400..aad33a35 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MaxPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()); + final Keyword enumKeyword = new MaxPropertiesKeyword(BigInteger.valueOf(2)); assertThat(enumKeyword.hasName("maxProperties"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -52,7 +48,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()).printOn(new HashMapMedia()), + new MaxPropertiesKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxProperties"), is(BigInteger.valueOf(2))) ); } @@ -60,9 +56,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MaxPropertiesKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -70,7 +64,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_for_less_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).build()), is(true) @@ -80,7 +74,7 @@ void should_be_valid_for_less_properties() { @Test void should_be_valid_exact_properties_count() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).add("bar", "hi").build()), is(true) @@ -90,18 +84,10 @@ void should_be_valid_exact_properties_count() { @Test void should_be_invalid_for_more_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).add("bar", "hi").add("baz", true).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType( - JsonProvider.provider(), - "maxProperties", - MaxPropertiesKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java index 10bbd699..9e57fc3f 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MaximumKeywordTest { @Test void should_know_his_name() { - final Keyword maximum = createKeywordFrom( - Json.createObjectBuilder().add("maximum", Json.createValue(1)).build() - ); + final Keyword maximum = new MaximumKeyword(BigDecimal.valueOf(1)); assertThat(maximum.hasName("maximum"), is(true)); assertThat(maximum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MaximumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,46 +56,26 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(11)), + new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(11)), is(false) ); } @Test void should_be_valid_for_equals_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(10)), - is(true) - ); + assertThat(new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(10)), is(true)); } @Test void shhould_be_valid_for_smaller_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(9)), - is(true) - ); + assertThat(new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(9)), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()).printOn( - new HashMapMedia() - ), + new MaximumKeyword(BigDecimal.valueOf(10)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maximum"), is(BigDecimal.valueOf(10))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "maximum", MaximumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java index 645feeb1..aaf6f2e0 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java @@ -28,16 +28,10 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import java.util.List; import org.hamcrest.Matcher; @@ -47,10 +41,7 @@ class MinContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MinContainsKeyword( - List.of(new StaticAnnotation("", JsonValue.NULL)), - BigInteger.ONE - ); + final Keyword enumKeyword = new MinContainsKeyword(List.of(), BigInteger.ONE); assertThat(enumKeyword.hasName("minContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -59,7 +50,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()).printOn(new HashMapMedia()), + new MinContainsKeyword(List.of(), BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minContains"), is(BigInteger.valueOf(2))) ); } @@ -67,12 +58,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(), BigInteger.valueOf(2)) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -82,11 +68,9 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).build()), @@ -97,11 +81,9 @@ void should_be_valid_if_contains_applies_to_exact_count() { @Test void should_be_valid_if_contains_applies_to_more_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(3).add(4).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(2).add(3).add("bar").add("baz").build()), @@ -112,11 +94,9 @@ void should_be_valid_if_contains_applies_to_more_items() { @Test void should_be_valid_for_empty_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("const", 1)) - .add("minContains", 0) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().build())), + BigInteger.valueOf(0) ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_ARRAY), @@ -127,11 +107,9 @@ void should_be_valid_for_empty_arrays() { @Test void should_be_invalid_if_contains_applies_to_less_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(1).build()), @@ -142,12 +120,7 @@ void should_be_invalid_if_contains_applies_to_less_items() { @Test void should_be_valid_if_contains_applies_to_all_and_more_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(true) @@ -157,12 +130,7 @@ void should_be_valid_if_contains_applies_to_all_and_more_items_in_array() { @Test void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").build()), is(true) @@ -172,28 +140,10 @@ void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() @Test void should_be_invalid_if_contains_applies_to_all_and_less_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectsKeywordType( - "minContains", - List.of(new Affects("contains", new Affects.ReplaceKeyword())), - (a, s) -> - new IntegerKeywordType( - JsonProvider.provider(), - "minContains", - value -> new MinContainsKeyword(a, value) - ).createKeyword(s) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java index 1af26aa0..e25711f8 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinItemsKeywordTest { @Test void should_be_know_his_name() { - final Keyword minItems = createKeywordFrom( - Json.createObjectBuilder().add("minItems", Json.createValue(1)).build() - ); + final Keyword minItems = new MinItemsKeyword(BigInteger.valueOf(1)); assertThat(minItems.hasName("minItems"), is(true)); assertThat(minItems.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_be_know_his_name() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MinItemsKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,7 +56,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_for_arrays_with_equals_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) + new MinItemsKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).build()), is(true) @@ -74,7 +66,7 @@ void should_be_valid_for_arrays_with_equals_size() { @Test void should_be_valid_for_arrays_with_greater_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) + new MinItemsKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -84,9 +76,7 @@ void should_be_valid_for_arrays_with_greater_size() { @Test void should_be_invalid_for_arrays_with_smaller_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().build()), + new MinItemsKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(Json.createArrayBuilder().build()), is(false) ); } @@ -94,16 +84,8 @@ void should_be_invalid_for_arrays_with_smaller_size() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()).printOn( - new HashMapMedia() - ), + new MinItemsKeyword(BigInteger.valueOf(1)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minItems"), is(BigInteger.valueOf(1))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "minItems", MinItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java index 798ca931..d5b21c30 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinLengthKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("minLength", Json.createValue(1)).build() - ); + final Keyword keyword = new MinLengthKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("test"), is(false)); assertThat(keyword.hasName("minLength"), is(true)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_invalid_with_shorter_string() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("A")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("A")), is(false) ); } @@ -64,9 +56,7 @@ void should_be_invalid_with_shorter_string() { @Test void should_be_valid_with_string_with_equal_length() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("AB")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("AB")), is(true) ); } @@ -74,9 +64,7 @@ void should_be_valid_with_string_with_equal_length() { @Test void should_be_valid_with_string_that_is_longer() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("ABC")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("ABC")), is(true) ); } @@ -84,9 +72,7 @@ void should_be_valid_with_string_that_is_longer() { @Test void should_be_valid_for_non_string_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -94,16 +80,8 @@ void should_be_valid_for_non_string_values() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MinLengthKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minLength"), is(BigInteger.valueOf(2L))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "minLength", MinLengthKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java index 9f7597af..1af6fa00 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MinPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()); + final Keyword enumKeyword = new MinPropertiesKeyword(BigInteger.valueOf(1)); assertThat(enumKeyword.hasName("minProperties"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -52,7 +48,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()).printOn(new HashMapMedia()), + new MinPropertiesKeyword(BigInteger.valueOf(1)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minProperties"), is(BigInteger.valueOf(1))) ); } @@ -60,9 +56,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MinPropertiesKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -70,7 +64,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_for_excat_properties_count() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) + new MinPropertiesKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).build()), is(true) @@ -80,7 +74,7 @@ void should_be_valid_for_excat_properties_count() { @Test void should_be_valid_for_more_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) + new MinPropertiesKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).add("bar", "hi").build()), is(true) @@ -90,18 +84,10 @@ void should_be_valid_for_more_properties() { @Test void should_be_invalid_for_less_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 2).build()) + new MinPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType( - JsonProvider.provider(), - "minProperties", - MinPropertiesKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java index 456a9784..b40b6f82 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinimumKeywordTest { @Test void should_know_his_name() { - final Keyword minimum = createKeywordFrom( - Json.createObjectBuilder().add("minimum", Json.createValue(1)).build() - ); + final Keyword minimum = new MinimumKeyword(BigDecimal.valueOf(1)); assertThat(minimum.hasName("minimum"), is(true)); assertThat(minimum.hasName("test"), is(false)); @@ -54,56 +48,31 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MinimumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @Test void should_be_invalid_for_smaller_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(-1)), - is(false) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(-1)), is(false)); } @Test void should_be_valid_for_equals_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(0)), - is(true) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(0)), is(true)); } @Test void shhould_be_valid_for_greater_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(1)), - is(true) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(1)), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()).printOn( - new HashMapMedia() - ), + new MinimumKeyword(BigDecimal.valueOf(0)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minimum"), is(BigDecimal.valueOf(0))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "minimum", MinimumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java index ee677364..9f3a405c 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MultipleOfKeywordTest { @Test void should_know_his_name() { - final Keyword multipleOf = createKeywordFrom( - Json.createObjectBuilder().add("multipleOf", Json.createValue(10)).build() - ); + final Keyword multipleOf = new MultipleOfKeyword(BigDecimal.valueOf(10)); assertThat(multipleOf.hasName("multipleOf"), is(true)); assertThat(multipleOf.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MultipleOfKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_valid_for_a_multipleOf() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(1.5)).build()) - .asAssertion() - .isValidFor(Json.createValue(4.5)), + new MultipleOfKeyword(BigDecimal.valueOf(1.5)).asAssertion().isValidFor(Json.createValue(4.5)), is(true) ); } @@ -74,9 +64,7 @@ void should_be_valid_for_a_multipleOf() { @Test void should_be_invalid_for_non_multipleOf() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue(7)), + new MultipleOfKeyword(BigDecimal.valueOf(2)).asAssertion().isValidFor(Json.createValue(7)), is(false) ); } @@ -84,11 +72,7 @@ void should_be_invalid_for_non_multipleOf() { @Test void should_be_valid_for_any_int_if_multipleOf_is_1en8() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("multipleOf", Json.createValue(new BigDecimal("1e-8"))).build() - ) - .asAssertion() - .isValidFor(Json.createValue(12391239123L)), + new MultipleOfKeyword(new BigDecimal("1e-8")).asAssertion().isValidFor(Json.createValue(12391239123L)), is(true) ); } @@ -96,16 +80,8 @@ void should_be_valid_for_any_int_if_multipleOf_is_1en8() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MultipleOfKeyword(BigDecimal.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("multipleOf"), is(new BigDecimal(2))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "multipleOf", MultipleOfKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java index 817ff41a..96250f0e 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +38,7 @@ class PatternKeywordTest { @Test void should_be_know_his_name() { - final Keyword pattern = createKeywordFrom( - Json.createObjectBuilder().add("pattern", Json.createValue("a")).build() - ); + final Keyword pattern = new PatternKeyword("a"); assertThat(pattern.hasName("pattern"), is(true)); assertThat(pattern.hasName("test"), is(false)); @@ -52,22 +46,13 @@ void should_be_know_his_name() { @Test void should_be_valid_for_non_string_value() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("pattern", Json.createValue("a")).build()) - .asAssertion() - .isValidFor(JsonValue.TRUE), - is(true) - ); + assertThat(new PatternKeyword("a").asAssertion().isValidFor(JsonValue.TRUE), is(true)); } @Test void should_be_invalid_for_non_matching_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ) + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") .asAssertion() .isValidFor(Json.createValue("(888)555-1212 ext. 532")), is(false) @@ -77,11 +62,7 @@ void should_be_invalid_for_non_matching_value() { @Test void should_be_valid_matching_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ) + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") .asAssertion() .isValidFor(Json.createValue("(888)555-1212")), is(true) @@ -91,18 +72,8 @@ void should_be_valid_matching_value() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ).printOn(new HashMapMedia()), + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$").printOn(new HashMapMedia()), (Matcher) hasEntry(is("pattern"), is("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "pattern", PatternKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java index f832e374..35374374 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java @@ -29,14 +29,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -44,9 +41,7 @@ class RequiredKeywordTest { @Test void should_know_his_name() { - final Keyword required = createKeywordFrom( - Json.createObjectBuilder().add("required", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword required = new RequiredKeyword(List.of()); assertThat(required.hasName("required"), is(true)); assertThat(required.hasName("test"), is(false)); @@ -55,9 +50,7 @@ void should_know_his_name() { @Test void should_invalid_if_not_all_properties_in_the_instance() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) + new RequiredKeyword(List.of("foo", "bar")) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", BigDecimal.ONE).build()), is(false) @@ -67,11 +60,7 @@ void should_invalid_if_not_all_properties_in_the_instance() { @Test void should_valid_for_non_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new RequiredKeyword(List.of("foo", "bar")).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -79,9 +68,7 @@ void should_valid_for_non_objects() { @Test void should_valid_if_all_properties_are_in_the_instance() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) + new RequiredKeyword(List.of("foo", "bar")) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", BigDecimal.ONE).add("bar", "test").build()), is(true) @@ -91,16 +78,8 @@ void should_valid_if_all_properties_are_in_the_instance() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ).printOn(new HashMapMedia()), + new RequiredKeyword(List.of("foo", "bar")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("required"), containsInAnyOrder("foo", "bar")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringArrayKeywordType(JsonProvider.provider(), "required", RequiredKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java index 86db461c..9904d662 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.BooleanKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.io.StringReader; import java.math.BigDecimal; import org.hamcrest.Matcher; @@ -44,9 +40,7 @@ class UniqueItemsKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build() - ); + final Keyword keyword = new UniqueItemsKeyword(false); assertThat(keyword.hasName("uniqueItems"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -55,9 +49,7 @@ void should_know_his_name() { @Test void should_be_valid_for_uniqueItems() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("2").build()), + new UniqueItemsKeyword(true).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("2").build()), is(true) ); } @@ -65,9 +57,7 @@ void should_be_valid_for_uniqueItems() { @Test void should_be_valid_for_non_uniqueItems_if_false() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("1").build()), + new UniqueItemsKeyword(false).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("1").build()), is(true) ); } @@ -75,27 +65,20 @@ void should_be_valid_for_non_uniqueItems_if_false() { @Test void should_be_invalid_for_non_uniqueItems() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("1").build()), + new UniqueItemsKeyword(true).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("1").build()), is(false) ); } @Test void should_be_valid_for_non_arrays() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), - is(true) - ); + assertThat(new UniqueItemsKeyword(false).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true)); } @Test void should_be_invalid_if_numbers_mathematically_unequal() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) + new UniqueItemsKeyword(true) .asAssertion() .isValidFor(Json.createReader(new StringReader("[1.0,1.00,1]")).readArray()), is(false) @@ -126,16 +109,8 @@ void pitests_say_i_must_write_this_tests() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()).printOn( - new HashMapMedia() - ), + new UniqueItemsKeyword(true).printOn(new HashMapMedia()), (Matcher) hasEntry(is("uniqueItems"), is(true)) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new BooleanKeywordType(JsonProvider.provider(), "uniqueItems", UniqueItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } }