diff --git a/core/pom.xml b/core/pom.xml index e5af61a6..833426d5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -81,14 +81,7 @@ com.google.errorprone error_prone_annotations test - - - - jakarta.json - jakarta.json-api - provided - - + org.eclipse.parsson parsson @@ -99,6 +92,17 @@ media-core test + + nl.jqno.equalsverifier + equalsverifier + test + + + + jakarta.json + jakarta.json-api + provided + com.github.spotbugs diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java new file mode 100644 index 00000000..de1ef5d6 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectByType.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; + +public enum AffectByType { + EXTENDS { + @Override + Keyword affect(final Keyword affectedKeyword) { + return affectedKeyword; + } + }, + REPLACE { + @Override + Keyword affect(final Keyword affectedKeyword) { + return new ReplacingKeyword(affectedKeyword); + } + }; + + abstract Keyword affect(final Keyword affectedKeyword); +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java new file mode 100644 index 00000000..0418be5c --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedBy.java @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +public final class AffectedBy implements Comparable { + + private final AffectByType type; + private final String name; + + public AffectedBy(final AffectByType type, final String name) { + this.type = Objects.requireNonNull(type); + this.name = Objects.requireNonNull(name); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(this.type); + hash = 83 * hash + Objects.hashCode(this.name); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return compareTo((AffectedBy) obj) == 0; + } + + @Override + public int compareTo(final AffectedBy other) { + final int result; + if (type.compareTo(other.type) == 0) { + result = name.compareTo(other.name); + } else { + result = type.compareTo(other.type); + } + return result; + } + + Function findAffectedByKeywordIn(final JsonSchema schema) { + final UnaryOperator result; + if (schema.keywordByName(name).isPresent()) { + result = type::affect; + } else { + result = k -> k; + } + return result; + } + + @Override + public String toString() { + return "AffectedBy{" + "type=" + type + ", name=" + name + '}'; + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java index ab528879..8a564151 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java @@ -23,27 +23,26 @@ */ package io.github.sebastiantoepfer.jsonschema.core.keyword.type; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toList; - import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Function; public class AffectedByKeywordType implements KeywordType { private final String name; - private final List affectedBy; - private final BiFunction, JsonSchema, Keyword> keywordCreator; + private final Collection affectedBy; + private final Function keywordCreator; public AffectedByKeywordType( final String name, - final List affectedBy, - final BiFunction, JsonSchema, Keyword> keywordCreator + final Collection affectedBy, + final Function keywordCreator ) { this.name = Objects.requireNonNull(name); this.affectedBy = List.copyOf(affectedBy); @@ -57,24 +56,24 @@ public String name() { @Override public Keyword createKeyword(final JsonSchema schema) { - return new AffectedByKeyword(schema, name, affectedBy, keywordCreator); + return new AffectedKeyword(schema, name, affectedBy, keywordCreator); } - static final class AffectedByKeyword extends KeywordRelationship { + static final class AffectedKeyword extends KeywordRelationship { private final JsonSchema schema; - private final List affectedBy; - private final BiFunction, JsonSchema, Keyword> keywordCreator; + private final SortedSet affectedBy; + private final Function keywordCreator; - public AffectedByKeyword( + public AffectedKeyword( final JsonSchema schema, final String name, - final List affectedBy, - final BiFunction, JsonSchema, Keyword> keywordCreator + final Collection affectedBy, + final Function keywordCreator ) { super(name); this.schema = Objects.requireNonNull(schema); - this.affectedBy = List.copyOf(affectedBy); + this.affectedBy = new TreeSet<>(affectedBy); this.keywordCreator = Objects.requireNonNull(keywordCreator); } @@ -82,9 +81,10 @@ public AffectedByKeyword( protected Keyword delegate() { return affectedBy .stream() - .map(schema::keywordByName) - .flatMap(Optional::stream) - .collect(collectingAndThen(toList(), k -> keywordCreator.apply(k, schema))); + .map(a -> a.findAffectedByKeywordIn(schema)) + .reduce(Function::andThen) + .orElseThrow() + .apply(keywordCreator.apply(schema)); } } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java new file mode 100644 index 00000000..8e9b59fe --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.toSet; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Assertion; +import io.github.sebastiantoepfer.jsonschema.keyword.Identifier; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.ReservedLocation; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; + +final class ReplacingKeyword implements Keyword { + + private final Keyword affectedKeyword; + private final Collection categoriesToReplace; + + public ReplacingKeyword(final Keyword affectedKeyword) { + this(affectedKeyword, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); + } + + public ReplacingKeyword(final Keyword affectedKeyword, final Collection categoriesToReplace) { + this.affectedKeyword = Objects.requireNonNull(affectedKeyword); + this.categoriesToReplace = List.copyOf(categoriesToReplace); + } + + @Override + public Identifier asIdentifier() { + if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asIdentifier(); + } + } + + @Override + public Assertion asAssertion() { + if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asAssertion(); + } + } + + @Override + public Annotation asAnnotation() { + if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asAnnotation(); + } + } + + @Override + public Applicator asApplicator() { + if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asApplicator(); + } + } + + @Override + public ReservedLocation asReservedLocation() { + if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) { + throw new UnsupportedOperationException(); + } else { + return affectedKeyword.asReservedLocation(); + } + } + + @Override + public Collection categories() { + return affectedKeyword.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); + } + + @Override + public boolean hasName(final String string) { + return affectedKeyword.hasName(string); + } + + @Override + public > T printOn(final T media) { + return affectedKeyword.printOn(media); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index 1dc844f2..e9cd01e7 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -24,6 +24,8 @@ package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator; import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectByType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedBy; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; @@ -56,12 +58,14 @@ public ApplicatorVocabulary() { new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - //normally affeced by minContains and maxContains, but only min has a direct effect! + new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, - List.of("minContains"), - (a, schema) -> - new SubSchemaKeywordType(ContainsKeyword.NAME, s -> new ContainsKeyword(a, s)).createKeyword(schema) + List.of( + new AffectedBy(AffectByType.REPLACE, "minContains"), + new AffectedBy(AffectByType.EXTENDS, "maxContains") + ), + new SubSchemaKeywordType(ContainsKeyword.NAME, ContainsKeyword::new)::createKeyword ) ); } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java index 49636d27..c22e044b 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeyword.java @@ -30,7 +30,6 @@ import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.util.Collection; @@ -54,10 +53,8 @@ final class ContainsKeyword implements Applicator, Annotation { static final String NAME = "contains"; private final JsonSchema contains; - private final List affectedBy; - public ContainsKeyword(final List affectedBy, final JsonSchema contains) { - this.affectedBy = List.copyOf(affectedBy); + public ContainsKeyword(final JsonSchema contains) { this.contains = Objects.requireNonNull(contains); } @@ -82,7 +79,7 @@ public boolean applyTo(final JsonValue instance) { } private boolean contains(final JsonArray array) { - return !affectedBy.isEmpty() || matchingValues(array).findAny().isPresent(); + return matchingValues(array).findAny().isPresent(); } @Override diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java new file mode 100644 index 00000000..bce5b3ab --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByTest.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +import java.util.List; +import java.util.TreeSet; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class AffectedByTest { + + @Test + void should_fullfil_equals_contract() { + EqualsVerifier.forClass(AffectedBy.class).withNonnullFields("type", "name").verify(); + } + + @Test + void should_be_sorted_correctly() { + assertThat( + new TreeSet<>( + List.of( + new AffectedBy(AffectByType.REPLACE, "d"), + new AffectedBy(AffectByType.EXTENDS, "c"), + new AffectedBy(AffectByType.EXTENDS, "b"), + new AffectedBy(AffectByType.REPLACE, "a") + ) + ), + contains( + new AffectedBy(AffectByType.EXTENDS, "b"), + new AffectedBy(AffectByType.EXTENDS, "c"), + new AffectedBy(AffectByType.REPLACE, "a"), + new AffectedBy(AffectByType.REPLACE, "d") + ) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java new file mode 100644 index 00000000..35ba659b --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import io.github.sebastiantoepfer.jsonschema.keyword.Assertion; +import io.github.sebastiantoepfer.jsonschema.keyword.Identifier; +import io.github.sebastiantoepfer.jsonschema.keyword.ReservedLocation; +import jakarta.json.JsonValue; +import java.net.URI; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +final class MockKeyword implements Identifier, Assertion, Annotation, Applicator, ReservedLocation { + + private final String name; + + public MockKeyword(final String name) { + this.name = name; + } + + @Override + public URI asUri() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isValidFor(final JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public JsonValue valueFor(final JsonValue value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean applyTo(final JsonValue instance) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection categories() { + return EnumSet.allOf(KeywordCategory.class); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(this.name, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(name, name); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java new file mode 100644 index 00000000..0f7c1a87 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeywordTest.java @@ -0,0 +1,176 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword.KeywordCategory; +import java.util.EnumSet; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class ReplacingKeywordTest { + + @Test + void should_not_be_createable_as_identifier_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.IDENTIFIER) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asIdentifier()); + } + + @Test + void should_return_identifier_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.IDENTIFIER)) + ).asIdentifier(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_assertion_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.ASSERTION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asAssertion()); + } + + @Test + void should_return_assertion_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.ASSERTION)) + ).asAssertion(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_annotation_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.ANNOTATION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asAnnotation()); + } + + @Test + void should_return_annotation_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.ANNOTATION)) + ).asAnnotation(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_applicator_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.APPLICATOR) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asApplicator()); + } + + @Test + void should_return_applicator_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.APPLICATOR)) + ).asApplicator(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_not_be_createable_as_reserved_location_if_it_should_replace_it() { + final Keyword keyword = new ReplacingKeyword( + new MockKeyword("test"), + EnumSet.of(Keyword.KeywordCategory.RESERVED_LOCATION) + ); + assertThrows(UnsupportedOperationException.class, () -> keyword.asReservedLocation()); + } + + @Test + void should_return_reserved_location_if_it_should_not_replaced() { + final Keyword keyword = new MockKeyword("test"); + assertThat( + new ReplacingKeyword( + keyword, + EnumSet.complementOf(EnumSet.of(Keyword.KeywordCategory.RESERVED_LOCATION)) + ).asReservedLocation(), + is(sameInstance(keyword)) + ); + } + + @Test + void should_name_of_decoded_keyword() { + final Keyword keyword = new ReplacingKeyword(new MockKeyword("test")); + + assertThat(keyword.hasName("test"), is(true)); + assertThat(keyword.hasName("bunny"), is(false)); + } + + @Test + void should_print_as_decored_keyword() { + assertThat( + new ReplacingKeyword(new MockKeyword("test")).printOn(new HashMapMedia()), + Matchers.hasEntry("test", "test") + ); + } + + @ParameterizedTest + @EnumSource(KeywordCategory.class) + void should_not_return_replaced_keyword_category(final Keyword.KeywordCategory category) { + assertThat( + new ReplacingKeyword(new MockKeyword("test"), EnumSet.of(category)).categories(), + both(not(hasItem(category))).and((Matcher) is(not(empty()))) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java index e6ddbad0..02c8c5f8 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ContainsKeywordTest.java @@ -30,14 +30,13 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectedByKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import java.util.List; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -56,10 +55,7 @@ void should_be_printable() { @Test void should_know_his_name() { - final Keyword enumKeyword = new ContainsKeyword( - List.of(), - new DefaultJsonSchemaFactory().create(JsonValue.TRUE) - ); + final Keyword enumKeyword = new ContainsKeyword(JsonSchemas.load(JsonValue.TRUE)); assertThat(enumKeyword.hasName("contains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -87,21 +83,6 @@ void should_apply_for_non_array() { ); } - @Test - void should_apply_to_empty_array_if_min_andor_max_provided() { - assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "number")) - .add("minContains", 0) - .build() - ) - .asApplicator() - .applyTo(JsonValue.EMPTY_JSON_ARRAY), - is(true) - ); - } - @Test void should_not_apply_to_empty_array_if_non_min_andor_max_is_provided() { assertThat( @@ -232,10 +213,8 @@ void should_return_true_if_all_item_applies() { } private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectedByKeywordType( - "contains", - List.of("minContains", "maxContains"), - (a, schema) -> new SubSchemaKeywordType("contains", s -> new ContainsKeyword(a, s)).createKeyword(schema) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); + return new SubSchemaKeywordType("contains", ContainsKeyword::new).createKeyword( + new DefaultJsonSchemaFactory().create(json) + ); } }