From 86c6dc15bb8cef637909e172a28de347ee0522a8 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Fri, 14 Jun 2024 19:46:21 +0200 Subject: [PATCH 1/2] prepare affectedBy for linkedWith --- .../core/keyword/type/AffectedBy.java | 61 +-------------- .../keyword/type/AffectedByKeywordType.java | 7 +- .../core/keyword/type/ExtendedBy.java | 70 +++++++++++++++++ .../core/keyword/type/ReplacedBy.java | 77 +++++++++++++++++++ .../applicator/ApplicatorVocabulary.java | 14 +--- .../core/keyword/type/ExtendedByTest.java} | 22 ++---- ...ffectedByTest.java => ReplacedByTest.java} | 31 +------- 7 files changed, 166 insertions(+), 116 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedBy.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacedBy.java rename core/src/{main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java => test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedByTest.java} (72%) rename core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/{AffectedByTest.java => ReplacedByTest.java} (56%) 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 index 0418be5c..f260be34 100644 --- 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 @@ -25,65 +25,8 @@ 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 + '}'; - } +public interface AffectedBy { + Function findAffectedByKeywordIn(JsonSchema schema); } 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 4f16fd38..8d816539 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 @@ -29,8 +29,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.Set; import java.util.function.Function; public final class AffectedByKeywordType implements KeywordType { @@ -65,7 +64,7 @@ public Keyword createKeyword(final JsonSchema schema) { static final class AffectedKeyword extends KeywordRelationship { private final JsonSchema schema; - private final SortedSet affectedBy; + private final Set affectedBy; private final Function keywordCreator; public AffectedKeyword( @@ -76,7 +75,7 @@ public AffectedKeyword( ) { super(name); this.schema = Objects.requireNonNull(schema); - this.affectedBy = new TreeSet<>(affectedBy); + this.affectedBy = Set.copyOf(affectedBy); this.keywordCreator = Objects.requireNonNull(keywordCreator); } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedBy.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedBy.java new file mode 100644 index 00000000..dcecbfa4 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedBy.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.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import java.util.Objects; +import java.util.function.Function; + +public final class ExtendedBy implements AffectedBy { + + private final String name; + + public ExtendedBy(final String name) { + this.name = name; + } + + @Override + public Function findAffectedByKeywordIn(final JsonSchema schema) { + return k -> k; + } + + @Override + public String toString() { + return "Extends{" + "name=" + name + '}'; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 89 * 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; + } + final ExtendedBy other = (ExtendedBy) obj; + return Objects.equals(this.name, other.name); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacedBy.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacedBy.java new file mode 100644 index 00000000..92d119af --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacedBy.java @@ -0,0 +1,77 @@ +/* + * 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 ReplacedBy implements AffectedBy { + + private final String name; + + public ReplacedBy(final String name) { + this.name = name; + } + + @Override + public Function findAffectedByKeywordIn(final JsonSchema schema) { + final UnaryOperator result; + if (schema.keywordByName(name).isPresent()) { + result = ReplacingKeyword::new; + } else { + result = k -> k; + } + return result; + } + + @Override + public String toString() { + return "Replace{" + "name=" + name + '}'; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * 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; + } + final ReplacedBy other = (ReplacedBy) obj; + return Objects.equals(this.name, other.name); + } +} 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 9d542a89..15a9b376 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,12 +24,12 @@ 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.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ExtendedBy; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ReplacedBy; 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; @@ -79,10 +79,7 @@ public ApplicatorVocabulary(final JsonProvider provider) { //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") - ), + List.of(new ExtendedBy("minItems"), new ExtendedBy("maxItems")), //nomally affectedBy too ... but we had the needed function only in affects :( schema -> new AffectsKeywordType( @@ -98,10 +95,7 @@ public ApplicatorVocabulary(final JsonProvider provider) { new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, - List.of( - new AffectedBy(AffectByType.REPLACE, "minContains"), - new AffectedBy(AffectByType.EXTENDS, "maxContains") - ), + List.of(new ReplacedBy("minContains"), new ExtendedBy("maxContains")), new SubSchemaKeywordType(ContainsKeyword.NAME, ContainsKeyword::new)::createKeyword ) ); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedByTest.java similarity index 72% rename from core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java rename to core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedByTest.java index de1ef5d6..1027c022 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ExtendedByTest.java @@ -23,21 +23,13 @@ */ package io.github.sebastiantoepfer.jsonschema.core.keyword.type; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; -public enum AffectByType { - EXTENDS { - @Override - Keyword affect(final Keyword affectedKeyword) { - return affectedKeyword; - } - }, - REPLACE { - @Override - Keyword affect(final Keyword affectedKeyword) { - return new ReplacingKeyword(affectedKeyword); - } - }; +class ExtendedByTest { - abstract Keyword affect(final Keyword affectedKeyword); + @Test + void equalsContract() { + EqualsVerifier.forClass(ExtendedBy.class).verify(); + } } 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/ReplacedByTest.java similarity index 56% rename from core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java rename to core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacedByTest.java index bce5b3ab..d3349144 100644 --- 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/ReplacedByTest.java @@ -23,38 +23,13 @@ */ 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(); - } +class ReplacedByTest { @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") - ) - ); + void equalsContract() { + EqualsVerifier.forClass(ReplacedBy.class).verify(); } } From baadf1748a9261964d24d078fb2f49e6d61ef342 Mon Sep 17 00:00:00 2001 From: Sebastian Toepfer <61313468+sebastian-toepfer@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:16:06 +0200 Subject: [PATCH 2/2] add support for if-then-else keyword --- core/pom.xml | 1 + .../core/keyword/type/LINKTYPE.java | 44 +++++ .../core/keyword/type/LinkedKeyword.java | 70 +++++++ .../core/keyword/type/LinkedWith.java | 77 ++++++++ .../core/keyword/type/ReplacingKeyword.java | 2 +- .../applicator/ApplicatorVocabulary.java | 20 ++ .../core/vocab/applicator/ElseKeyword.java | 67 +++++++ .../core/vocab/applicator/IfKeyword.java | 67 +++++++ .../core/vocab/applicator/ThenKeyword.java | 68 +++++++ .../core/keyword/type/LinkedKeywordTest.java | 181 ++++++++++++++++++ .../core/keyword/type/LinkedWithTest.java | 68 +++++++ .../core/keyword/type/MockKeyword.java | 8 +- .../vocab/applicator/ElseKeywordTest.java | 82 ++++++++ .../core/vocab/applicator/IfKeywordTest.java | 82 ++++++++ .../vocab/applicator/ThenKeywordTest.java | 82 ++++++++ 15 files changed, 917 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java create mode 100644 core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java create mode 100644 core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java diff --git a/core/pom.xml b/core/pom.xml index bc1ccafc..cf471b0a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -136,6 +136,7 @@ + **/tests/draft2020-12/if-then-else.json **/tests/draft2020-12/items.json **/tests/draft2020-12/maxContains.json **/tests/draft2020-12/maxItems.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java new file mode 100644 index 00000000..9b973cbd --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java @@ -0,0 +1,44 @@ +/* + * 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 jakarta.json.JsonValue; +import java.util.function.Predicate; + +public enum LINKTYPE { + VALID { + @Override + Predicate connect(final Predicate first, final Predicate second) { + return first.negate().or(second); + } + }, + INVALID { + @Override + Predicate connect(final Predicate first, final Predicate second) { + return first.or(second); + } + }; + + abstract Predicate connect(Predicate first, Predicate second); +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java new file mode 100644 index 00000000..102d13b2 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.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.keyword.type; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +final class LinkedKeyword implements Applicator { + + private final Keyword firstChainLink; + private final LINKTYPE linkType; + private final Keyword secondChanLink; + + public LinkedKeyword(final Keyword firstChainLink, final LINKTYPE linkType, final Keyword secondChanLink) { + this.firstChainLink = Objects.requireNonNull(firstChainLink); + this.linkType = Objects.requireNonNull(linkType); + this.secondChanLink = Objects.requireNonNull(secondChanLink); + } + + @Override + public Collection categories() { + final Set result = EnumSet.copyOf(firstChainLink.categories()); + result.addAll(secondChanLink.categories()); + return result; + } + + @Override + public boolean applyTo(final JsonValue instance) { + return linkType + .connect(firstChainLink.asApplicator()::applyTo, secondChanLink.asApplicator()::applyTo) + .test(instance); + } + + @Override + public boolean hasName(final String name) { + return secondChanLink.hasName(name); + } + + @Override + public > T printOn(final T media) { + return secondChanLink.printOn(media); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java new file mode 100644 index 00000000..5b4c1ae8 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java @@ -0,0 +1,77 @@ +/* + * 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.JsonSubSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import java.util.Objects; +import java.util.function.Function; + +public final class LinkedWith implements AffectedBy { + + private final String name; + private final Function keywordCreator; + private final LINKTYPE linkType; + + public LinkedWith( + final String name, + final Function keywordCreator, + final LINKTYPE linkType + ) { + this.name = Objects.requireNonNull(name); + this.keywordCreator = Objects.requireNonNull(keywordCreator); + this.linkType = Objects.requireNonNull(linkType); + } + + @Override + public Function findAffectedByKeywordIn(final JsonSchema schema) { + return schema + .subSchema(name) + .map(keywordCreator) + .map(k -> (Function) new KeywordLink(k, linkType)) + .orElse(ReplacingKeyword::new); + } + + @Override + public String toString() { + return "LinkedWith{" + "name=" + name + '}'; + } + + private static class KeywordLink implements Function { + + private final Keyword firstChainLink; + private final LINKTYPE linkType; + + public KeywordLink(final Keyword firstChainLink, final LINKTYPE linkType) { + this.firstChainLink = Objects.requireNonNull(firstChainLink); + this.linkType = Objects.requireNonNull(linkType); + } + + @Override + public Keyword apply(final Keyword secondChanLink) { + return new LinkedKeyword(firstChainLink, linkType, secondChanLink); + } + } +} 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 39d5a619..25e0a33f 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 @@ -38,7 +38,7 @@ import java.util.List; import java.util.Objects; -final class ReplacingKeyword implements Keyword { +public final class ReplacingKeyword implements Keyword { private final Keyword keywordToReplace; private final Collection categoriesToReplace; 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 15a9b376..a0c60159 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 @@ -28,16 +28,21 @@ 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.ExtendedBy; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.LINKTYPE; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.LinkedWith; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ReplacedBy; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ReplacingKeyword; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.Json; import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import java.net.URI; +import java.util.EnumSet; import java.util.List; import java.util.Optional; @@ -58,6 +63,21 @@ public ApplicatorVocabulary(final JsonProvider provider) { new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new), + new AffectedByKeywordType( + ThenKeyword.NAME, + List.of(new LinkedWith(IfKeyword.NAME, IfKeyword::new, LINKTYPE.VALID)), + new SubSchemaKeywordType(ThenKeyword.NAME, ThenKeyword::new)::createKeyword + ), + //if keyword as no meanings without then or else -> needs a better affects keywordtype + new SubSchemaKeywordType( + IfKeyword.NAME, + schema -> new ReplacingKeyword(new IfKeyword(schema), EnumSet.allOf(Keyword.KeywordCategory.class)) + ), + new AffectedByKeywordType( + ElseKeyword.NAME, + List.of(new LinkedWith(IfKeyword.NAME, IfKeyword::new, LINKTYPE.INVALID)), + new SubSchemaKeywordType(ElseKeyword.NAME, ElseKeyword::new)::createKeyword + ), new SubSchemaKeywordType(NotKeyword.NAME, NotKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), //nomally affectedBy ... but we had the needed function only in affects :( diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java new file mode 100644 index 00000000..53386623 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * else : Schema
+ * When if is present, and the instance fails to validate against its subschema, then validation succeeds against this + * keyword if the instance successfully validates against this keyword’s subschema.
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/else/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.3 + */ +class ElseKeyword implements Applicator { + + static final String NAME = "else"; + private final JsonSchema schema; + + public ElseKeyword(final JsonSchema schema) { + this.schema = Objects.requireNonNull(schema); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schema); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schema.validator().isValid(instance); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java new file mode 100644 index 00000000..23fbf9be --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * if : Schema
+ * This keyword declares a condition based on the validation result of the given schema.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/if/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.1 + */ +final class IfKeyword implements Applicator { + + static final String NAME = "if"; + private final JsonSchema schema; + + public IfKeyword(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/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java new file mode 100644 index 00000000..294e40bb --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.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; + +/** + * then : Schema
+ * When if is present, and the instance successfully validates against its subschema, then validation succeeds against + * this keyword if the instance also successfully validates against this keyword’s subschema.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/then/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.2 + */ +final class ThenKeyword implements Applicator { + + static final String NAME = "then"; + private final JsonSchema schema; + + public ThenKeyword(final JsonSchema schema) { + this.schema = Objects.requireNonNull(schema); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schema); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schema.validator().isValid(instance); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java new file mode 100644 index 00000000..f9a7db3a --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java @@ -0,0 +1,181 @@ +/* + * 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.containsInAnyOrder; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.JsonValue; +import java.util.Collection; +import java.util.EnumSet; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class LinkedKeywordTest { + + @Test + void should_return_categories_from_both_keywords() { + assertThat( + new LinkedKeyword(new MockAnnotation(), LINKTYPE.VALID, new MockApplicator()).categories(), + containsInAnyOrder(Keyword.KeywordCategory.ANNOTATION, Keyword.KeywordCategory.APPLICATOR) + ); + } + + @Nested + class LinkTypeIsValid { + + @Test + void should_valid_if_both_are_valid() { + assertThat( + new LinkedKeyword(new MockApplicator(true), LINKTYPE.VALID, new MockApplicator(true)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(true) + ); + } + + @Test + void should_invalid_if_first_is_valid_and_second_is_invalid() { + assertThat( + new LinkedKeyword(new MockApplicator(true), LINKTYPE.VALID, new MockApplicator(false)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(false) + ); + } + + @Test + void should_valid_if_first_is_invalid() { + assertThat( + new LinkedKeyword(new MockApplicator(false), LINKTYPE.VALID, new MockApplicator(true)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(true) + ); + } + } + + @Nested + class LinkTypeIsInvalid { + + @Test + void should_be_valid_if_first_is_valid() { + assertThat( + new LinkedKeyword(new MockApplicator(true), LINKTYPE.INVALID, new MockApplicator(false)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(true) + ); + } + + @Test + void should_be_valid_if_first_is_invalid_and_second_is_valid() { + assertThat( + new LinkedKeyword(new MockApplicator(false), LINKTYPE.INVALID, new MockApplicator(true)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(true) + ); + } + + @Test + void should_be_invalid_if_both_are_invalid() { + assertThat( + new LinkedKeyword(new MockApplicator(false), LINKTYPE.INVALID, new MockApplicator(false)) + .asApplicator() + .applyTo(JsonValue.TRUE), + is(false) + ); + } + } + + @Test + void should_know_his_name() { + final Keyword keyword = new LinkedKeyword(new MockKeyword("if"), LINKTYPE.VALID, new MockKeyword("then")); + + assertThat(keyword.hasName("if"), is(false)); + assertThat(keyword.hasName("then"), is(true)); + } + + @Test + void should_use_second_to_print() { + assertThat( + new LinkedKeyword(new MockKeyword("if"), LINKTYPE.VALID, new MockKeyword("then")).printOn( + new HashMapMedia() + ), + is(new MockKeyword("then").printOn(new HashMapMedia())) + ); + } + + private static final class MockAnnotation implements Keyword { + + @Override + public Collection categories() { + return EnumSet.of(KeywordCategory.ANNOTATION, KeywordCategory.APPLICATOR); + } + + @Override + public boolean hasName(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public > T printOn(T media) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + private static final class MockApplicator implements Applicator { + + private final boolean result; + + public MockApplicator() { + this(true); + } + + public MockApplicator(final boolean result) { + this.result = result; + } + + @Override + public boolean applyTo(final JsonValue instance) { + return result; + } + + @Override + public boolean hasName(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public > T printOn(T media) { + throw new UnsupportedOperationException("Not supported yet."); + } + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java new file mode 100644 index 00000000..c0f09255 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.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.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; + +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import java.util.EnumSet; +import org.junit.jupiter.api.Test; + +class LinkedWithTest { + + @Test + void should_replace_keyword_if_previous_chain_link_not_available() { + assertThat( + new LinkedWith( + "if", + s -> new MockKeyword("if", EnumSet.of(Keyword.KeywordCategory.APPLICATOR)), + LINKTYPE.VALID + ) + .findAffectedByKeywordIn(JsonSchemas.load(JsonValue.TRUE)) + .apply(new MockKeyword("MOCK")) + .categories(), + not(hasItems(Keyword.KeywordCategory.ASSERTION, Keyword.KeywordCategory.APPLICATOR)) + ); + } + + @Test + void should_chain_keyword_if_previous_chain_link_is_available() { + assertThat( + new LinkedWith( + "if", + s -> new MockKeyword("if", EnumSet.of(Keyword.KeywordCategory.APPLICATOR)), + LINKTYPE.VALID + ) + .findAffectedByKeywordIn(JsonSchemas.load(Json.createObjectBuilder().add("if", JsonValue.TRUE).build())) + .apply(new ReplacingKeyword(new MockKeyword("MOCK"), EnumSet.of(Keyword.KeywordCategory.ASSERTION))) + .categories(), + hasItems(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) + ); + } +} 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 index 35ba659b..b1639ce8 100644 --- 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 @@ -38,9 +38,15 @@ final class MockKeyword implements Identifier, Assertion, Annotation, Applicator, ReservedLocation { private final String name; + private final Collection categories; public MockKeyword(final String name) { + this(name, EnumSet.allOf(KeywordCategory.class)); + } + + public MockKeyword(final String name, final Collection categories) { this.name = name; + this.categories = EnumSet.copyOf(categories); } @Override @@ -65,7 +71,7 @@ public boolean applyTo(final JsonValue instance) { @Override public Collection categories() { - return EnumSet.allOf(KeywordCategory.class); + return EnumSet.copyOf(categories); } @Override diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java new file mode 100644 index 00000000..4023f6cf --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java @@ -0,0 +1,82 @@ +/* + * 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.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class ElseKeywordTest { + + @Test + void should_know_his_name() { + final Keyword keyword = new ElseKeyword(JsonSchemas.load(JsonValue.TRUE)); + + assertThat(keyword.hasName("else"), is(true)); + assertThat(keyword.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new ElseKeyword( + 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("else"), hasKey("properties")) + ); + } + + @Test + void should_be_valid_if_instance_successful_validates_against_schema() { + assertThat( + new ElseKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(true) + ); + } + + @Test + void should_be_invalid_if_instance_not_successful_validates_against_schema() { + assertThat( + new ElseKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(false) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java new file mode 100644 index 00000000..f9d85004 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java @@ -0,0 +1,82 @@ +/* + * 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.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class IfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword keyword = new IfKeyword(JsonSchemas.load(JsonValue.TRUE)); + + assertThat(keyword.hasName("if"), is(true)); + assertThat(keyword.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new IfKeyword( + 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("if"), hasKey("properties")) + ); + } + + @Test + void should_be_valid_if_instance_successful_validates_against_schema() { + assertThat( + new IfKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(true) + ); + } + + @Test + void should_be_invalid_if_instance_not_successful_validates_against_schema() { + assertThat( + new IfKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(false) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java new file mode 100644 index 00000000..17d0e288 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java @@ -0,0 +1,82 @@ +/* + * 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.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class ThenKeywordTest { + + @Test + void should_know_his_name() { + final Keyword keyword = new ThenKeyword(JsonSchemas.load(JsonValue.TRUE)); + + assertThat(keyword.hasName("then"), is(true)); + assertThat(keyword.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new ThenKeyword( + 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("then"), hasKey("properties")) + ); + } + + @Test + void should_be_valid_if_instance_successful_validates_against_schema() { + assertThat( + new ThenKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(true) + ); + } + + @Test + void should_be_invalid_if_instance_not_successful_validates_against_schema() { + assertThat( + new ThenKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(false) + ); + } +}