diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java index 8a564151..4f16fd38 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectedByKeywordType.java @@ -33,7 +33,7 @@ import java.util.TreeSet; import java.util.function.Function; -public class AffectedByKeywordType implements KeywordType { +public final class AffectedByKeywordType implements KeywordType { private final String name; private final Collection affectedBy; @@ -44,6 +44,9 @@ public AffectedByKeywordType( final Collection affectedBy, final Function keywordCreator ) { + if (affectedBy.isEmpty()) { + throw new IllegalArgumentException("affectedBy can not be empty!"); + } this.name = Objects.requireNonNull(name); this.affectedBy = List.copyOf(affectedBy); this.keywordCreator = Objects.requireNonNull(keywordCreator); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java new file mode 100644 index 00000000..62c181d3 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/Affects.java @@ -0,0 +1,94 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; +import jakarta.json.JsonValue; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +public final class Affects { + + private final String name; + private final AbsenceStrategy strategy; + + public Affects(final String name, final JsonValue answerInAbsence) { + this(name, new ProvideDefaultValue(answerInAbsence)); + } + + public Affects(final String name, final AbsenceStrategy strategy) { + this.name = Objects.requireNonNull(name); + this.strategy = Objects.requireNonNull(strategy); + } + + @Override + public String toString() { + return "Affects{" + "name=" + name + ", strategy=" + strategy.getClass() + '}'; + } + + Map.Entry> findAffectsKeywordIn(final JsonSchema schema) { + final Map.Entry> result; + final Optional annotation = schema + .keywordByName(name) + .filter(k -> k.hasCategory(Keyword.KeywordCategory.ANNOTATION)) + .map(Keyword::asAnnotation); + if (annotation.isPresent()) { + result = Map.entry(annotation.get(), k -> k); + } else { + result = strategy.create(name); + } + return result; + } + + public interface AbsenceStrategy { + Map.Entry> create(String name); + } + + public static final class ReplaceKeyword implements AbsenceStrategy { + + @Override + public Map.Entry> create(final String name) { + return Map.entry(new StaticAnnotation(name, JsonValue.NULL), ReplacingKeyword::new); + } + } + + public static final class ProvideDefaultValue implements AbsenceStrategy { + + private final JsonValue answerInAbsence; + + public ProvideDefaultValue(final JsonValue answerInAbsence) { + this.answerInAbsence = Objects.requireNonNullElse(answerInAbsence, JsonValue.NULL); + } + + @Override + public Map.Entry> create(final String name) { + return Map.entry(new StaticAnnotation(name, answerInAbsence), k -> k); + } + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java index 33a27e87..5cf8620d 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsKeywordType.java @@ -27,24 +27,27 @@ import io.github.sebastiantoepfer.jsonschema.keyword.Annotation; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; -import jakarta.json.JsonValue; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; +import java.util.function.Function; public class AffectsKeywordType implements KeywordType { private final String name; - private final String affects; - private final BiFunction keywordCreator; + private final Collection affects; + private final BiFunction, JsonSchema, Keyword> keywordCreator; public AffectsKeywordType( final String name, - final String affects, - final BiFunction keywordCreator + final Collection affects, + final BiFunction, JsonSchema, Keyword> keywordCreator ) { - this.name = name; - this.affects = affects; + this.name = Objects.requireNonNull(name); + this.affects = List.copyOf(affects); this.keywordCreator = keywordCreator; } @@ -55,36 +58,46 @@ public String name() { @Override public Keyword createKeyword(final JsonSchema schema) { - return new AffectsKeyword(schema, name, affects, keywordCreator); + return new AffectsKeyword(schema, name, List.copyOf(affects), keywordCreator); } static final class AffectsKeyword extends KeywordRelationship { private final JsonSchema schema; - private final String affects; - private final BiFunction keywordCreator; + private final Collection affects; + private final BiFunction, JsonSchema, Keyword> keywordCreator; public AffectsKeyword( final JsonSchema schema, final String name, - final String affects, - final BiFunction keywordCreator + final List affects, + final BiFunction, JsonSchema, Keyword> keywordCreator ) { super(name); this.schema = Objects.requireNonNull(schema); - this.affects = affects; + this.affects = List.copyOf(affects); this.keywordCreator = Objects.requireNonNull(keywordCreator); } @Override protected Keyword delegate() { - return keywordCreator.apply( - schema - .keywordByName(affects) - .map(Keyword::asAnnotation) - .orElseGet(() -> new StaticAnnotation(affects, JsonValue.NULL)), - schema - ); + //ugly ... map.entry is not the right structure ... + final Map.Entry, Function> p = affects + .stream() + .map(a -> a.findAffectsKeywordIn(schema)) + .reduce( + Map.entry(List.of(), k -> k), + ( + Map.Entry, Function> t, + Map.Entry> u + ) -> { + final ArrayList newAnnotations = new ArrayList<>(t.getKey()); + newAnnotations.add(u.getKey()); + return Map.entry(newAnnotations, t.getValue().andThen(u.getValue())); + }, + (l, r) -> null + ); + return p.getValue().apply(keywordCreator.apply(p.getKey(), schema)); } } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java index 8e9b59fe..39d5a619 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java @@ -40,15 +40,15 @@ final class ReplacingKeyword implements Keyword { - private final Keyword affectedKeyword; + private final Keyword keywordToReplace; private final Collection categoriesToReplace; - public ReplacingKeyword(final Keyword affectedKeyword) { - this(affectedKeyword, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); + public ReplacingKeyword(final Keyword keywordToReplace) { + this(keywordToReplace, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); } - public ReplacingKeyword(final Keyword affectedKeyword, final Collection categoriesToReplace) { - this.affectedKeyword = Objects.requireNonNull(affectedKeyword); + public ReplacingKeyword(final Keyword keywordToReplace, final Collection categoriesToReplace) { + this.keywordToReplace = Objects.requireNonNull(keywordToReplace); this.categoriesToReplace = List.copyOf(categoriesToReplace); } @@ -57,7 +57,7 @@ public Identifier asIdentifier() { if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asIdentifier(); + return keywordToReplace.asIdentifier(); } } @@ -66,7 +66,7 @@ public Assertion asAssertion() { if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asAssertion(); + return keywordToReplace.asAssertion(); } } @@ -75,7 +75,7 @@ public Annotation asAnnotation() { if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asAnnotation(); + return keywordToReplace.asAnnotation(); } } @@ -84,7 +84,7 @@ public Applicator asApplicator() { if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asApplicator(); + return keywordToReplace.asApplicator(); } } @@ -93,22 +93,22 @@ public ReservedLocation asReservedLocation() { if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) { throw new UnsupportedOperationException(); } else { - return affectedKeyword.asReservedLocation(); + return keywordToReplace.asReservedLocation(); } } @Override public Collection categories() { - return affectedKeyword.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); + return keywordToReplace.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); } @Override public boolean hasName(final String string) { - return affectedKeyword.hasName(string); + return keywordToReplace.hasName(string); } @Override public > T printOn(final T media) { - return affectedKeyword.printOn(media); + return keywordToReplace.printOn(media); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java index e9cd01e7..d3bd39fc 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -58,7 +58,7 @@ public ApplicatorVocabulary() { new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), + new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), new AffectedByKeywordType( ContainsKeyword.NAME, List.of( diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java index e9e51768..94d9fada 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeyword.java @@ -30,6 +30,8 @@ import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.math.BigInteger; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -47,11 +49,11 @@ final class MaxContainsKeyword implements Assertion { static final String NAME = "maxContains"; - private final Annotation affects; + private final Collection affects; private final BigInteger maxContains; - public MaxContainsKeyword(final Annotation affects, final BigInteger maxContains) { - this.affects = Objects.requireNonNull(affects); + public MaxContainsKeyword(final Collection affects, final BigInteger maxContains) { + this.affects = List.copyOf(affects); this.maxContains = Objects.requireNonNull(maxContains); } @@ -67,20 +69,21 @@ public > T printOn(final T media) { @Override public boolean isValidFor(final JsonValue instance) { - return ( - !InstanceType.ARRAY.isInstance(instance) || isValidFor(affects.valueFor(instance), instance.asJsonArray()) + return (!InstanceType.ARRAY.isInstance(instance) || isValidFor(instance.asJsonArray())); + } + + private boolean isValidFor(final JsonArray instance) { + return isValidFor( + affects + .stream() + .map(a -> a.valueFor(instance)) + .map(v -> new NumberOfMatches(instance, v)) + .mapToInt(NumberOfMatches::count) + .sum() ); } - private boolean isValidFor(final JsonValue containing, final JsonArray values) { - final boolean result; - if (JsonValue.NULL.equals(containing)) { - result = true; - } else if (JsonValue.TRUE.equals(containing)) { - result = values.size() <= maxContains.intValue(); - } else { - result = containing.asJsonArray().size() <= maxContains.intValue(); - } - return result; + private boolean isValidFor(final int containing) { + return containing <= maxContains.intValue(); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java index 4b6ef50b..e8277014 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeyword.java @@ -30,6 +30,8 @@ import jakarta.json.JsonArray; import jakarta.json.JsonValue; import java.math.BigInteger; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -47,11 +49,11 @@ final class MinContainsKeyword implements Assertion { static final String NAME = "minContains"; - private final Annotation affects; + private final Collection affects; private final BigInteger minContains; - public MinContainsKeyword(final Annotation affects, final BigInteger minContains) { - this.affects = Objects.requireNonNull(affects); + public MinContainsKeyword(final Collection affects, final BigInteger minContains) { + this.affects = List.copyOf(affects); this.minContains = Objects.requireNonNull(minContains); } @@ -67,20 +69,21 @@ public > T printOn(final T media) { @Override public boolean isValidFor(final JsonValue instance) { - return ( - !InstanceType.ARRAY.isInstance(instance) || isValidFor(affects.valueFor(instance), instance.asJsonArray()) + return (!InstanceType.ARRAY.isInstance(instance) || isValidFor(instance.asJsonArray())); + } + + private boolean isValidFor(final JsonArray instance) { + return isValidFor( + affects + .stream() + .map(a -> a.valueFor(instance)) + .map(v -> new NumberOfMatches(instance, v)) + .mapToInt(NumberOfMatches::count) + .sum() ); } - private boolean isValidFor(final JsonValue containing, final JsonArray values) { - final boolean result; - if (JsonValue.NULL.equals(containing)) { - result = true; - } else if (JsonValue.TRUE.equals(containing)) { - result = values.size() >= minContains.intValue(); - } else { - result = containing.asJsonArray().size() >= minContains.intValue(); - } - return result; + private boolean isValidFor(final int containing) { + return containing >= minContains.intValue(); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java new file mode 100644 index 00000000..bb8f07da --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/NumberOfMatches.java @@ -0,0 +1,43 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.vocab.validation; + +import jakarta.json.JsonArray; +import jakarta.json.JsonValue; +import java.util.Objects; + +final class NumberOfMatches { + + private final JsonArray array; + private final JsonValue value; + + public NumberOfMatches(final JsonArray array, final JsonValue value) { + this.array = Objects.requireNonNull(array); + this.value = Objects.requireNonNull(value); + } + + public int count() { + return value == JsonValue.TRUE ? array.size() : value.asJsonArray().size(); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java index abc376e7..86e69b80 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ValidationVocabulary.java @@ -24,6 +24,7 @@ package io.github.sebastiantoepfer.jsonschema.core.vocab.validation; import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AnyKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArrayKeywordType; @@ -37,6 +38,7 @@ import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; +import java.util.List; import java.util.Optional; public final class ValidationVocabulary implements Vocabulary { @@ -65,7 +67,7 @@ public ValidationVocabulary(final JsonProvider jsonContext) { new IntegerKeywordType(jsonContext, MinItemsKeyword.NAME, MinItemsKeyword::new), new AffectsKeywordType( MaxContainsKeyword.NAME, - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (affects, schema) -> new IntegerKeywordType( JsonProvider.provider(), @@ -75,13 +77,13 @@ public ValidationVocabulary(final JsonProvider jsonContext) { ), new AffectsKeywordType( MinContainsKeyword.NAME, - "contains", - (a, s) -> + List.of(new Affects("contains", new Affects.ReplaceKeyword())), + (affects, schema) -> new IntegerKeywordType( JsonProvider.provider(), MinContainsKeyword.NAME, - value -> new MinContainsKeyword(a, value) - ).createKeyword(s) + value -> new MinContainsKeyword(affects, value) + ).createKeyword(schema) ), new BooleanKeywordType(jsonContext, UniqueItemsKeyword.NAME, UniqueItemsKeyword::new) ); diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java new file mode 100644 index 00000000..5e13cce3 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/AffectsTest.java @@ -0,0 +1,99 @@ +/* + * The MIT License + * + * Copyright 2024 sebastian. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.github.sebastiantoepfer.jsonschema.core.keyword.type; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.junit.jupiter.api.Test; + +class AffectsTest { + + @Test + void should_handle_non_annotation_as_absence() { + assertThat( + new Affects("type", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) + .getKey() + .valueFor(JsonValue.FALSE), + is(JsonValue.EMPTY_JSON_OBJECT) + ); + } + + @Test + void should_return_original_annotation() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("test", Json.createObjectBuilder().add("type", "number")) + ) + .build() + ) + ) + .getKey() + .valueFor(Json.createObjectBuilder().add("test", 1L).build()), + is(Json.createArrayBuilder().add("test").build()) + ); + } + + @Test + void should_create_original_annotation() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("test", Json.createObjectBuilder().add("type", "number")) + ) + .build() + ) + ) + .getValue() + .apply(new MockKeyword("test")) + .hasName("test"), + is(true) + ); + } + + @Test + void should_create_original_annotation_also_for_missing() { + assertThat( + new Affects("properties", new Affects.ProvideDefaultValue(JsonValue.EMPTY_JSON_OBJECT)) + .findAffectsKeywordIn(JsonSchemas.load(JsonValue.TRUE)) + .getValue() + .apply(new MockKeyword("test")) + .hasName("test"), + is(true) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java index 2b7a3861..bff8d9b2 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxContainsKeywordTest.java @@ -29,6 +29,7 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; @@ -38,6 +39,7 @@ import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import java.math.BigInteger; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -45,7 +47,10 @@ class MaxContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MaxContainsKeyword(new StaticAnnotation("", JsonValue.NULL), BigInteger.ONE); + final Keyword enumKeyword = new MaxContainsKeyword( + List.of(new StaticAnnotation("", JsonValue.NULL)), + BigInteger.ONE + ); assertThat(enumKeyword.hasName("maxContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); } @@ -61,23 +66,18 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()) + createKeywordFrom( + Json.createObjectBuilder() + .add("contains", Json.createObjectBuilder().add("type", "string")) + .add("maxContains", 2) + .build() + ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } - @Test - void should_be_valid_if_no_contains_is_present() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("foo").build()), - is(true) - ); - } - @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( @@ -186,7 +186,7 @@ void should_be_invalid_if_contains_applies_to_all_and_more_items_in_array() { private static Keyword createKeywordFrom(final JsonObject json) { return new AffectsKeywordType( "maxContains", - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (a, s) -> new IntegerKeywordType( JsonProvider.provider(), diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java index 682bcab6..645feeb1 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinContainsKeywordTest.java @@ -29,6 +29,7 @@ import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; @@ -38,6 +39,7 @@ import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import java.math.BigInteger; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -45,7 +47,10 @@ class MinContainsKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = new MinContainsKeyword(new StaticAnnotation("", JsonValue.NULL), BigInteger.ONE); + final Keyword enumKeyword = new MinContainsKeyword( + List.of(new StaticAnnotation("", JsonValue.NULL)), + BigInteger.ONE + ); assertThat(enumKeyword.hasName("minContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -62,23 +67,18 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()) + createKeywordFrom( + Json.createObjectBuilder() + .add("contains", Json.createObjectBuilder().add("type", "string")) + .add("minContains", 2) + .build() + ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } - @Test - void should_be_valid_if_no_contains_is_present() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("foo").build()), - is(true) - ); - } - @Test void should_be_valid_if_contains_applies_to_exact_count() { assertThat( @@ -187,7 +187,7 @@ void should_be_invalid_if_contains_applies_to_all_and_less_items_in_array() { private static Keyword createKeywordFrom(final JsonObject json) { return new AffectsKeywordType( "minContains", - "contains", + List.of(new Affects("contains", new Affects.ReplaceKeyword())), (a, s) -> new IntegerKeywordType( JsonProvider.provider(),