diff --git a/core/pom.xml b/core/pom.xml index bc1ccaf..cf471b0 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 0000000..9b973cb --- /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 0000000..102d13b --- /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 0000000..5b4c1ae --- /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 39d5a61..25e0a33 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 15a9b37..a0c6015 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 0000000..5338662 --- /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 0000000..23fbf9b --- /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 0000000..294e40b --- /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 0000000..f9a7db3 --- /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 0000000..c0f0925 --- /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 35ba659..b1639ce 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 0000000..4023f6c --- /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 0000000..f9d8500 --- /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 0000000..17d0e28 --- /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) + ); + } +}