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)
+ );
}
}