diff --git a/README.md b/README.md index 94317dbd..3f9e87b0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=sebastian-toepfer_json-schema&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=sebastian-toepfer_json-schema) -[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#io.github.sebastian-toepfer.json-schema:json-schema) +![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) +![Sonar Tests](https://img.shields.io/sonar/tests/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) +![Sonar Violations](https://img.shields.io/sonar/violations/sebastian-toepfer_json-schema?server=https%3A%2F%2Fsonarcloud.io) + +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/sebastian-toepfer/json-schema/build.yml) + +![Maven Central Version](https://img.shields.io/maven-central/v/io.github.sebastian-toepfer.json-schema/json-schema) +![GitHub Release](https://img.shields.io/github/v/release/sebastian-toepfer/json-schema) +![GitHub commits since latest release](https://img.shields.io/github/commits-since/sebastian-toepfer/json-schema/latest) + +[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/io/github/sebastian-toepfer/json-schema/json-schema/README.md) # json-schema json schema for json-api diff --git a/api/pom.xml b/api/pom.xml index 1389663e..96e27abf 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.1 + 0.3.0-SNAPSHOT json-schema-api diff --git a/core/pom.xml b/core/pom.xml index 92c55112..8f77065e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.1 + 0.3.0-SNAPSHOT json-schema-core @@ -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 @@ -117,10 +121,14 @@ **/tests/draft2019-09/additionalItems.json --> **/tests/draft2020-12/additionalProperties.json + **/tests/draft2020-12/allOf.json + **/tests/draft2020-12/anyOf.json + **/tests/draft2020-12/oneOf.json **/tests/draft2020-12/boolean_schema.json **/tests/draft2020-12/const.json **/tests/draft2020-12/contains.json **/tests/draft2020-12/dependentRequired.json + **/tests/draft2020-12/dependentSchemas.json **/tests/draft2020-12/enum.json **/tests/draft2020-12/exclusiveMaximum.json **/tests/draft2020-12/exclusiveMinimum.json @@ -139,6 +147,9 @@ **/tests/draft2020-12/minLength.json **/tests/draft2020-12/minimum.json **/tests/draft2020-12/minProperties.json + **/tests/draft2020-12/multipleOf.json **/tests/draft2020-12/pattern.json **/tests/draft2020-12/patternProperties.json diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java index 09a7ea53..a916aa78 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/AbstractJsonValueSchema.java @@ -51,4 +51,9 @@ public final JsonObject asJsonObject() { public final JsonArray asJsonArray() { return value.asJsonArray(); } + + @Override + public String toString() { + return value.toString(); + } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java index ea5b225b..639dc4f6 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchema.java @@ -116,4 +116,9 @@ public ValueType getValueType() { public JsonObject asJsonObject() { return schema.asJsonObject(); } + + @Override + public String toString() { + return schema.toString(); + } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java index 8ee64c12..df243365 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/Keywords.java @@ -29,7 +29,11 @@ import io.github.sebastiantoepfer.jsonschema.JsonSchema; import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.applicator.ApplicatorVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.content.ContentVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatAnnotationVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.meta.MetaDataVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.unevaluated.UnevaluatedVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.validation.ValidationVocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; @@ -54,7 +58,14 @@ final class Keywords { .stream() .collect(toMap(Vocabulary::id, Function.identity())); - DEFAULT_VOCABS = List.of(new ValidationVocabulary(JsonProvider.provider()), new ApplicatorVocabulary()); + DEFAULT_VOCABS = List.of( + new ApplicatorVocabulary(), + new ValidationVocabulary(JsonProvider.provider()), + new MetaDataVocabulary(), + new FormatAnnotationVocabulary(), + new UnevaluatedVocabulary(), + new ContentVocabulary() + ); } private final Collection vocabularies; @@ -63,8 +74,8 @@ public Keywords(final Collection vocabDefs) { if ( vocabDefs .stream() - .filter(vocabDef -> MANDANTORY_VOCABS.containsKey(vocabDef.id())) - .anyMatch(not(VocabularyDefinition::required)) + .filter(vocabDef -> MANDANTORY_VOCABS.keySet().stream().anyMatch(vocabDef::hasid)) + .anyMatch(not(VocabularyDefinition::isRequired)) ) { throw new IllegalArgumentException("can not be created without core vocabulary is requiered!"); } 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..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 @@ -23,28 +23,30 @@ */ 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 { +public final 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 ) { + 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); @@ -57,24 +59,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 +84,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/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 new file mode 100644 index 00000000..39d5a619 --- /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 keywordToReplace; + private final Collection categoriesToReplace; + + public ReplacingKeyword(final Keyword keywordToReplace) { + this(keywordToReplace, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION)); + } + + public ReplacingKeyword(final Keyword keywordToReplace, final Collection categoriesToReplace) { + this.keywordToReplace = Objects.requireNonNull(keywordToReplace); + this.categoriesToReplace = List.copyOf(categoriesToReplace); + } + + @Override + public Identifier asIdentifier() { + if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) { + throw new UnsupportedOperationException(); + } else { + return keywordToReplace.asIdentifier(); + } + } + + @Override + public Assertion asAssertion() { + if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) { + throw new UnsupportedOperationException(); + } else { + return keywordToReplace.asAssertion(); + } + } + + @Override + public Annotation asAnnotation() { + if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) { + throw new UnsupportedOperationException(); + } else { + return keywordToReplace.asAnnotation(); + } + } + + @Override + public Applicator asApplicator() { + if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) { + throw new UnsupportedOperationException(); + } else { + return keywordToReplace.asApplicator(); + } + } + + @Override + public ReservedLocation asReservedLocation() { + if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) { + throw new UnsupportedOperationException(); + } else { + return keywordToReplace.asReservedLocation(); + } + } + + @Override + public Collection categories() { + return keywordToReplace.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet()); + } + + @Override + public boolean hasName(final String string) { + return keywordToReplace.hasName(string); + } + + @Override + public > T printOn(final T media) { + return keywordToReplace.printOn(media); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java similarity index 91% rename from core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java rename to core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java index 77ac2eb4..9dcbeb2b 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ArraySubSchemaKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/SchemaArrayKeywordType.java @@ -33,12 +33,12 @@ import java.util.Objects; import java.util.function.Function; -public final class ArraySubSchemaKeywordType implements KeywordType { +public final class SchemaArrayKeywordType implements KeywordType { private final String name; private final Function, Keyword> keywordCreator; - public ArraySubSchemaKeywordType(final String name, final Function, Keyword> keywordCreator) { + public SchemaArrayKeywordType(final String name, final Function, Keyword> keywordCreator) { this.name = Objects.requireNonNull(name); this.keywordCreator = Objects.requireNonNull(keywordCreator); } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java index 306bde21..78d53762 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/OfficialVocabularies.java @@ -27,7 +27,7 @@ import io.github.sebastiantoepfer.jsonschema.core.vocab.applicator.ApplicatorVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.content.ContentVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; -import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatVocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.format.FormatAnnotationVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.meta.MetaDataVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.unevaluated.UnevaluatedVocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.validation.ValidationVocabulary; @@ -49,7 +49,7 @@ public final class OfficialVocabularies implements LazyVocabularies { new ApplicatorVocabulary(), new ValidationVocabulary(JSONP), new MetaDataVocabulary(), - new FormatVocabulary(), + new FormatAnnotationVocabulary(), new UnevaluatedVocabulary(), new ContentVocabulary() ); diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java index 85714078..52571818 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeyword.java @@ -23,9 +23,11 @@ */ package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator; +import static java.util.function.Predicate.not; + import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.InstanceType; -import io.github.sebastiantoepfer.jsonschema.JsonSubSchema; +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; @@ -38,8 +40,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; import java.util.stream.Stream; /** @@ -61,10 +61,15 @@ final class AdditionalPropertiesKeyword implements Applicator, Annotation { static final String NAME = "additionalProperties"; - private final JsonSubSchema additionalPropertiesSchema; + private final Collection affectedBy; + private final JsonSchema additionalPropertiesSchema; - public AdditionalPropertiesKeyword(final JsonSubSchema additionalPropertiesSchema) { + public AdditionalPropertiesKeyword( + final Collection affectedBy, + final JsonSchema additionalPropertiesSchema + ) { this.additionalPropertiesSchema = additionalPropertiesSchema; + this.affectedBy = List.copyOf(affectedBy); } @Override @@ -93,16 +98,12 @@ public JsonValue valueFor(final JsonValue instance) { private Stream> findPropertiesForValidation(final JsonObject instance) { final Collection ignoredProperties = findPropertyNamesAlreadyConveredByOthersIn(instance); - return instance.entrySet().stream().filter(Predicate.not(e -> ignoredProperties.contains(e.getKey()))); + return instance.entrySet().stream().filter(not(e -> ignoredProperties.contains(e.getKey()))); } private Collection findPropertyNamesAlreadyConveredByOthersIn(final JsonValue instance) { - return Stream.of( - additionalPropertiesSchema.owner().keywordByName("properties"), - additionalPropertiesSchema.owner().keywordByName("patternProperties") - ) - .flatMap(Optional::stream) - .map(Keyword::asAnnotation) + return affectedBy + .stream() .map(anno -> anno.valueFor(instance)) .map(JsonValue::asJsonArray) .flatMap(Collection::stream) diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.java new file mode 100644 index 00000000..b78dfe6e --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeyword.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.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.Collection; +import java.util.List; +import java.util.Objects; + +/** + * allOf : Array
+ * An instance validates successfully against this keyword if it validates successfully against all schemas defined by + * this keyword’s value.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/allof/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.1 + */ +final class AllOfKeyword implements Applicator { + + static final String NAME = "allOf"; + private final Collection schemas; + + public AllOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).allMatch(v -> v.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, schemas); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java new file mode 100644 index 00000000..79db69af --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeyword.java @@ -0,0 +1,57 @@ +/* + * 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.Collection; +import java.util.List; +import java.util.Objects; + +final class AnyOfKeyword implements Applicator { + + static final String NAME = "anyOf"; + private final Collection schemas; + + public AnyOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).anyMatch(v -> v.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, schemas); + } +} 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 ba3a0076..75384f96 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java @@ -24,12 +24,18 @@ 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.ArraySubSchemaKeywordType; +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.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; +import jakarta.json.Json; +import jakarta.json.JsonValue; import java.net.URI; import java.util.List; import java.util.Optional; @@ -46,19 +52,55 @@ public final class ApplicatorVocabulary implements Vocabulary { private final Vocabulary vocab; public ApplicatorVocabulary() { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/applicator"), + new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new), + new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new), + new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new), + new SubSchemaKeywordType(NotKeyword.NAME, NotKeyword::new), new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new), - new SubSchemaKeywordType(AdditionalPropertiesKeyword.NAME, AdditionalPropertiesKeyword::new), + //nomally affectedBy ... but we had the needed function only in affects :( + new AffectsKeywordType( + AdditionalPropertiesKeyword.NAME, + List.of( + new Affects("properties", JsonValue.EMPTY_JSON_ARRAY), + new Affects("patternProperties", JsonValue.EMPTY_JSON_ARRAY) + ), + (affects, schema) -> + new SubSchemaKeywordType( + AdditionalPropertiesKeyword.NAME, + s -> new AdditionalPropertiesKeyword(affects, s) + ).createKeyword(schema) + ), new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new), - new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new), - new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new), - //normally affeced by minContains and maxContains, but only min has a direct effect! + new NamedJsonSchemaKeywordType(DependentSchemasKeyword.NAME, DependentSchemasKeyword::new), + //this example shows my missunderstanding from affects, affectedBy and keywordtypes :( + new AffectedByKeywordType( + ItemsKeyword.NAME, + List.of( + new AffectedBy(AffectByType.EXTENDS, "minItems"), + new AffectedBy(AffectByType.EXTENDS, "maxItems") + ), + //nomally affectedBy too ... but we had the needed function only in affects :( + schema -> + new AffectsKeywordType( + ItemsKeyword.NAME, + List.of(new Affects("prefixItems", Json.createValue(-1))), + (affects, subSchema) -> + new SubSchemaKeywordType( + ItemsKeyword.NAME, + s -> new ItemsKeyword(affects, s) + ).createKeyword(subSchema) + ).createKeyword(schema) + ), + new SchemaArrayKeywordType(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/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java new file mode 100644 index 00000000..eda475eb --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeyword.java @@ -0,0 +1,69 @@ +/* + * 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.InstanceType; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import java.util.Objects; +import java.util.Optional; + +final class DependentSchemasKeyword implements Applicator { + + static final String NAME = "dependentSchemas"; + private final NamedJsonSchemas schemas; + + public DependentSchemasKeyword(final NamedJsonSchemas schemas) { + this.schemas = schemas; + } + + @Override + public boolean applyTo(final JsonValue instance) { + return !InstanceType.OBJECT.isInstance(instance) || applyTo(instance.asJsonObject()); + } + + private boolean applyTo(final JsonObject instance) { + return instance + .keySet() + .stream() + .map(schemas::schemaWithName) + .flatMap(Optional::stream) + .map(JsonSchema::validator) + .allMatch(v -> v.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, schemas); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java index 2a8acd31..0591c7ed 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeyword.java @@ -25,17 +25,16 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.InstanceType; -import io.github.sebastiantoepfer.jsonschema.JsonSubSchema; -import io.github.sebastiantoepfer.jsonschema.Validator; +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.JsonNumber; import jakarta.json.JsonValue; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; /** * items : Schema
@@ -56,9 +55,11 @@ final class ItemsKeyword implements Applicator, Annotation { static final String NAME = "items"; - private final JsonSubSchema schema; + private final Collection affectedBys; + private final JsonSchema schema; - public ItemsKeyword(final JsonSubSchema schema) { + public ItemsKeyword(final Collection affectedBys, final JsonSchema schema) { + this.affectedBys = List.copyOf(affectedBys); this.schema = Objects.requireNonNull(schema); } @@ -89,28 +90,35 @@ public JsonValue valueFor(final JsonValue value) { } private boolean appliesToAnyFor(final JsonArray value) { - return startIndexFor(value) == -1; + return itemsForValidation(value).anyMatch(schema.validator()::isValid); } @Override public boolean applyTo(final JsonValue instance) { - return !InstanceType.ARRAY.isInstance(instance) || matchesSchema(instance.asJsonArray()); + return !InstanceType.ARRAY.isInstance(instance) || applyTo(instance.asJsonArray()); } - private boolean matchesSchema(final JsonArray items) { - final Validator itemValidator = schema.validator(); - return items.stream().skip(startIndexFor(items) + 1L).allMatch(itemValidator::isValid); + private boolean applyTo(final JsonArray items) { + return itemsForValidation(items).allMatch(schema.validator()::isValid); + } + + private Stream itemsForValidation(final JsonArray items) { + return items.stream().skip(startIndexFor(items) + 1L); } private int startIndexFor(final JsonArray value) { - return schema - .owner() - .keywordByName("prefixItems") - .map(Keyword::asAnnotation) - .map(anno -> anno.valueFor(value)) - .map(v -> new MaxIndexCalculator(value, v)) - .map(MaxIndexCalculator::maxIndex) - .orElse(-1); + final int result; + if (affectedBys.isEmpty()) { + result = -1; + } else { + result = affectedBys + .stream() + .map(anno -> anno.valueFor(value)) + .map(v -> new MaxIndexCalculator(value, v)) + .mapToInt(MaxIndexCalculator::maxIndex) + .sum(); + } + return result; } private static class MaxIndexCalculator { diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.java new file mode 100644 index 00000000..dbda60e0 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeyword.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; + +/** + * not : Schema
+ * An instance is valid against this keyword if it fails to validate successfully against the schema defined by + * this keyword.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/not/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.4 + */ +final class NotKeyword implements Applicator { + + static final String NAME = "not"; + private final JsonSchema schema; + + public NotKeyword(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/OneOfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.java new file mode 100644 index 00000000..a3054172 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeyword.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.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.Collection; +import java.util.List; +import java.util.Objects; + +/** + * oneOf : Array
+ * An instance validates successfully against this keyword if it validates successfully against exactly one schema + * defined by this keyword’s value.
+ *
+ * kind + *
    + *
  • Applicator
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/applicator/oneof/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.1.3 + */ +final class OneOfKeyword implements Applicator { + + static final String NAME = "oneOf"; + private final Collection schemas; + + public OneOfKeyword(final Collection schemas) { + this.schemas = List.copyOf(schemas); + } + + @Override + public boolean applyTo(final JsonValue instance) { + return schemas.stream().map(JsonSchema::validator).filter(v -> v.isValid(instance)).limit(2).count() == 1; + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, schemas); + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java index 2948db29..ef6e04e5 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/content/ContentVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class ContentVocabulary implements Vocabulary { private final Vocabulary vocab; public ContentVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/content")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/content")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java index 26673186..0232c6fb 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CoreVocabulary.java @@ -27,7 +27,7 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; import java.util.Optional; @@ -37,7 +37,7 @@ public final class CoreVocabulary implements Vocabulary { private final Vocabulary vocab; public CoreVocabulary(final JsonProvider jsonContext) { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/core"), new StringKeywordType(jsonContext, SchemaKeyword.NAME, value -> new SchemaKeyword(URI.create(value))), new StringKeywordType(jsonContext, IdKeyword.NAME, value -> new IdKeyword(URI.create(value))), diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java index de5037d0..c31901b0 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeyword.java @@ -25,11 +25,9 @@ import io.github.sebastiantoepfer.ddd.common.Media; import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Applicator; import jakarta.json.Json; import jakarta.json.JsonPointer; -import jakarta.json.JsonReader; import jakarta.json.JsonValue; import java.io.IOException; import java.net.URI; @@ -51,10 +49,12 @@ final class RefKeyword implements Applicator { static final String NAME = "$ref"; private final JsonSchema schema; private final URI uri; + private final SchemaRegistry schemaRegistry; - public RefKeyword(final JsonSchema schema, final URI uri) { + public RefKeyword(final JsonSchema schema, final URI uri, final SchemaRegistry schemaRegistry) { this.schema = Objects.requireNonNull(schema); this.uri = Objects.requireNonNull(uri); + this.schemaRegistry = Objects.requireNonNull(schemaRegistry); } @Override @@ -76,9 +76,9 @@ private JsonSchema retrieveJsonSchema() { final JsonSchema json; try { if (isRemote()) { - json = retrieveValueFromRemoteLocation(); + json = retrieveSchemaFromRegistry(); } else { - json = retrieveValueFromLocalSchema(); + json = retrieveSchemaFromLocalSchema(); } return json; } catch (IOException ex) { @@ -86,7 +86,7 @@ private JsonSchema retrieveJsonSchema() { } } - private JsonSchema retrieveValueFromLocalSchema() throws IOException { + private JsonSchema retrieveSchemaFromLocalSchema() throws IOException { return schema.rootSchema().subSchema(createPointer()).orElseThrow(); } @@ -101,10 +101,8 @@ private JsonPointer createPointer() { return pointer; } - private JsonSchema retrieveValueFromRemoteLocation() throws IOException { - try (final JsonReader reader = Json.createReader(uri.toURL().openStream())) { - return JsonSchemas.load(reader.readValue()); - } + private JsonSchema retrieveSchemaFromRegistry() throws IOException { + return schemaRegistry.schemaForUrl(uri); } private boolean isRemote() { diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java index dcff9353..93dd6b77 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordType.java @@ -34,9 +34,11 @@ final class RefKeywordType implements KeywordType { private final JsonProvider jsonContext; + private final SchemaRegistry schemaRegistry; public RefKeywordType(final JsonProvider jsonContext) { this.jsonContext = Objects.requireNonNull(jsonContext); + this.schemaRegistry = new SchemaRegistry.RemoteSchemaRegistry(); } @Override @@ -49,7 +51,7 @@ public Keyword createKeyword(final JsonSchema schema) { return new StringKeywordType( jsonContext, RefKeyword.NAME, - s -> new RefKeyword(schema, URI.create(s)) + s -> new RefKeyword(schema, URI.create(s), schemaRegistry) ).createKeyword(schema); } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.java new file mode 100644 index 00000000..1fb4ffe9 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistry.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.vocab.core; + +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import jakarta.json.Json; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; +import java.io.IOException; +import java.net.URI; + +interface SchemaRegistry { + JsonSchema schemaForUrl(URI uri) throws IOException; + + class DefaultSchemaRegistry implements SchemaRegistry { + + private JsonSchema schema; + + public DefaultSchemaRegistry() { + schema = JsonSchemas.load(JsonValue.FALSE); + } + + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + return schema; + } + } + + class RemoteSchemaRegistry implements SchemaRegistry { + + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + try (JsonReader reader = Json.createReader(uri.toURL().openStream())) { + return JsonSchemas.load(reader.readValue()); + } + } + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java new file mode 100644 index 00000000..1bff8b10 --- /dev/null +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeyword.java @@ -0,0 +1,127 @@ +/* + * 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.core; + +import io.github.sebastiantoepfer.ddd.common.Media; +import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.core.vocab.OfficialVocabularies; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularyDefinition; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * $vocabulary : Object<URI, Boolean>
+ * This keyword is used in meta-schemas to identify the required and optional vocabularies available for use in
+ * schemas described by that meta-schema.
+ *
+ *
    + *
  • identifier
  • + *
+ * + * source: https://www.learnjsonschema.com/2020-12/core/vocabulary/ + * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.1.2 + */ +final class VocabularyKeyword implements VocabularyDefinitions { + + static final String NAME = "$vocabulary"; + private final JsonObject vocabularies; + private final Supplier> lazyVocabulariesSupplier; + + VocabularyKeyword(final JsonValue vocabularies, final Supplier> lazyVocabulariesSupplier) { + this(vocabularies.asJsonObject(), lazyVocabulariesSupplier); + } + + VocabularyKeyword( + final JsonObject vocabularies, + final Supplier> lazyVocabulariesSupplier + ) { + this.vocabularies = Objects.requireNonNull(vocabularies); + this.lazyVocabulariesSupplier = Objects.requireNonNull(lazyVocabulariesSupplier); + } + + @Override + public > T printOn(final T media) { + return media.withValue(NAME, new JsonObjectPrintable(vocabularies)); + } + + @Override + public boolean hasName(final String name) { + return Objects.equals(NAME, name); + } + + @Override + public Collection categories() { + //is a identifier after spec ... but how to implement it as it? + return List.of(); + } + + @Override + public Stream definitions() { + return vocabularies.entrySet().stream().map(LazyNonOfficalVocabularyDefinition::new); + } + + private final class LazyNonOfficalVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final JsonValue required; + + public LazyNonOfficalVocabularyDefinition(final Map.Entry property) { + this(URI.create(property.getKey()), property.getValue()); + } + + public LazyNonOfficalVocabularyDefinition(final URI id, final JsonValue required) { + this.id = Objects.requireNonNull(id); + this.required = required; + } + + @Override + public Optional findVocabulary() { + return new OfficialVocabularies() + .loadVocabularyWithId(id) + .or(() -> new LazyVocabularyDefinition(id, isRequired(), lazyVocabulariesSupplier).findVocabulary()); + } + + @Override + public boolean hasid(final URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return JsonValue.TRUE.equals(required); + } + } +} diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java index 1bbac9ae..78cff548 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordType.java @@ -23,38 +23,35 @@ */ package io.github.sebastiantoepfer.jsonschema.core.vocab.core; -import io.github.sebastiantoepfer.ddd.common.Media; -import io.github.sebastiantoepfer.ddd.media.json.JsonObjectPrintable; import io.github.sebastiantoepfer.jsonschema.InstanceType; import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ServiceLoaderLazyVocabulariesSupplier; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import java.net.URI; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; /** * see: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-vocabulary-keyword */ public final class VocabularyKeywordType implements KeywordType { + private final ServiceLoaderLazyVocabulariesSupplier lazyVocabulariesSupplier; + + public VocabularyKeywordType() { + this.lazyVocabulariesSupplier = new ServiceLoaderLazyVocabulariesSupplier(); + } + @Override public String name() { - return "$vocabulary"; + return VocabularyKeyword.NAME; } @Override - public VocabularyKeyword createKeyword(final JsonSchema schema) { + public VocabularyDefinitions createKeyword(final JsonSchema schema) { final JsonValue value = schema.asJsonObject().get((name())); final VocabularyKeyword result; if (InstanceType.OBJECT.isInstance(value)) { - result = new VocabularyKeyword(value); + result = new VocabularyKeyword(value, lazyVocabulariesSupplier); } else { throw new IllegalArgumentException( "must be an object! " + @@ -64,53 +61,4 @@ public VocabularyKeyword createKeyword(final JsonSchema schema) { } return result; } - - /** - * $vocabulary : Object<URI, Boolean>
- * This keyword is used in meta-schemas to identify the required and optional vocabularies available for use in
- * schemas described by that meta-schema.
- *
- *
    - *
  • identifier
  • - *
- * - * source: https://www.learnjsonschema.com/2020-12/core/vocabulary/ - * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.1.2 - */ - public final class VocabularyKeyword implements Keyword, VocabularyDefinitions { - - private final JsonObject vocabularies; - - VocabularyKeyword(final JsonValue vocabularies) { - this(vocabularies.asJsonObject()); - } - - VocabularyKeyword(final JsonObject vocabularies) { - this.vocabularies = Objects.requireNonNull(vocabularies); - } - - @Override - public > T printOn(final T media) { - return media.withValue(name(), new JsonObjectPrintable(vocabularies)); - } - - @Override - public boolean hasName(final String name) { - return Objects.equals(name(), name); - } - - @Override - public Collection categories() { - //is a identifier after spec ... but how to implement it as it? - return List.of(); - } - - @Override - public Stream definitions() { - return vocabularies - .entrySet() - .stream() - .map(entry -> new VocabularyDefinition(URI.create(entry.getKey()), entry.getValue() == JsonValue.TRUE)); - } - } } diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java similarity index 86% rename from core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java rename to core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java index 38cc30b5..1f918bdd 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/format/FormatAnnotationVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -36,12 +36,12 @@ * source: https://www.learnjsonschema.com/2020-12/format-annotation/ * spec: https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7.2.1 */ -public final class FormatVocabulary implements Vocabulary { +public final class FormatAnnotationVocabulary implements Vocabulary { private final Vocabulary vocab; - public FormatVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/format-annotation")); + public FormatAnnotationVocabulary() { + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/format-annotation")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java index aed1b3e1..b642fbee 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/meta/MetaDataVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class MetaDataVocabulary implements Vocabulary { private final Vocabulary vocab; public MetaDataVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/meta-data")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/meta-data")); } @Override diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java index e868460d..6ae14692 100644 --- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java +++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/unevaluated/UnevaluatedVocabulary.java @@ -25,7 +25,7 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import java.net.URI; import java.util.Optional; @@ -41,7 +41,7 @@ public final class UnevaluatedVocabulary implements Vocabulary { private final Vocabulary vocab; public UnevaluatedVocabulary() { - this.vocab = new DefaultVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/unevaluated")); + this.vocab = new ListVocabulary(URI.create("https://json-schema.org/draft/2020-12/vocab/unevaluated")); } @Override 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..9e4bd0f9 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; @@ -34,9 +35,10 @@ import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.DefaultVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; import jakarta.json.spi.JsonProvider; import java.net.URI; +import java.util.List; import java.util.Optional; public final class ValidationVocabulary implements Vocabulary { @@ -44,7 +46,7 @@ public final class ValidationVocabulary implements Vocabulary { private final Vocabulary vocab; public ValidationVocabulary(final JsonProvider jsonContext) { - this.vocab = new DefaultVocabulary( + this.vocab = new ListVocabulary( URI.create("https://json-schema.org/draft/2020-12/vocab/validation"), new TypeKeywordType(), new AnyKeywordType(jsonContext, ConstKeyword.NAME, ConstKeyword::new), @@ -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/DefaultJsonObjectSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java index 8c15f1fe..d8b2e54f 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonObjectSchemaTest.java @@ -145,4 +145,12 @@ void should_return_empty_if_given_name_not_resolve_to_a_valid_schematype() { void should_be_printable() { assertThat(schema.printOn(new HashMapMedia()), allOf(hasEntry("type", "array"), hasKey("items"))); } + + @Test + void should_has_a_nice_to_string() { + assertThat( + new DefaultJsonObjectSchema(Json.createObjectBuilder().add("type", "string").build()).toString(), + is("{\"type\":\"string\"}") + ); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java index e70f3787..d7a55369 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/DefaultJsonSubSchemaTest.java @@ -147,4 +147,15 @@ void should_be_printable() { hasEntry("type", "array") ); } + + @Test + void should_has_a_nice_to_string() { + assertThat( + new DefaultJsonSubSchema( + new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("test", JsonValue.TRUE).build()), + new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("type", "array").build()) + ).toString(), + is("{\"type\":\"array\"}") + ); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java index 93d6fd4a..8d106dd8 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/EmptyJsonSchemaTest.java @@ -45,4 +45,9 @@ void should_be_valid_for_everything(final JsonValue value) { void should_be_printable() { assertThat(new EmptyJsonSchema().printOn(new HashMapMedia()), anEmptyMap()); } + + @Test + void should_has_a_nice_to_string() { + assertThat(new EmptyJsonSchema().toString(), is("{}")); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java index cdd8215c..496213e3 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/FalseJsonSchemaTest.java @@ -28,6 +28,7 @@ import static org.hamcrest.Matchers.not; import jakarta.json.JsonValue; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -38,4 +39,9 @@ class FalseJsonSchemaTest { void should_be_invalid_for_everything(final JsonValue value) { assertThat(new FalseJsonSchema().validator().isValid(value), is(not(true))); } + + @Test + void should_has_a_nice_to_string() { + assertThat(new FalseJsonSchema().toString(), is("false")); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java index 0f42ad66..19c40788 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/KeywordsTest.java @@ -29,12 +29,15 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertThrows; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; import io.github.sebastiantoepfer.jsonschema.core.vocab.core.CoreVocabulary; import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; import jakarta.json.spi.JsonProvider; import java.net.URI; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.junit.jupiter.api.Test; class KeywordsTest { @@ -42,7 +45,7 @@ class KeywordsTest { @Test void should_not_be_createbale_without_mandantory_core_vocabulary() { final Collection vocabDefs = List.of( - new VocabularyDefinition(new CoreVocabulary(JsonProvider.provider()).id(), false) + new TestVocabularyDefinition(new CoreVocabulary(JsonProvider.provider()).id(), false) ); assertThrows(IllegalArgumentException.class, () -> new Keywords(vocabDefs)); } @@ -50,7 +53,7 @@ void should_not_be_createbale_without_mandantory_core_vocabulary() { @Test void should_not_be_createbale_without_mandantory_base_vocabulary() { final Collection vocabDefs = List.of( - new VocabularyDefinition(new BasicVocabulary().id(), false) + new TestVocabularyDefinition(new BasicVocabulary().id(), false) ); assertThrows(IllegalArgumentException.class, () -> new Keywords(vocabDefs)); } @@ -58,8 +61,38 @@ void should_not_be_createbale_without_mandantory_base_vocabulary() { @Test void should_be_createbale_with_optional_vocabularies() { assertThat( - new Keywords(List.of(new VocabularyDefinition(URI.create("http://optinal"), false))), + new Keywords(List.of(new TestVocabularyDefinition(URI.create("http://optinal"), false))), is(not(nullValue())) ); } + + private static final class TestVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final boolean required; + + public TestVocabularyDefinition(final URI id) { + this(id, false); + } + + public TestVocabularyDefinition(final URI id, final boolean required) { + this.id = id; + this.required = required; + } + + @Override + public Optional findVocabulary() { + return Optional.empty(); + } + + @Override + public boolean hasid(URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return required; + } + } } 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/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/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/AdditionalPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java index 053791be..711ca283 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AdditionalPropertiesKeywordTest.java @@ -30,12 +30,12 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +43,7 @@ class AdditionalPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new AdditionalPropertiesKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)); assertThat(keyword.hasName("additionalProperties"), is(true)); assertThat(keyword.hasName("test"), is(false)); } @@ -53,11 +51,9 @@ void should_know_his_name() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", JsonValue.EMPTY_JSON_ARRAY)), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_ARRAY), @@ -68,11 +64,9 @@ void should_be_valid_for_non_objects() { @Test void should_not_valid_if_no_additionals_are_allow() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()), @@ -83,11 +77,9 @@ void should_not_valid_if_no_additionals_are_allow() { @Test void should_valid_if_additionals_are_allow() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.TRUE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.TRUE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()), @@ -98,11 +90,9 @@ void should_valid_if_additionals_are_allow() { @Test void should_valid_if_no_additionals_are_allow_and_no_additionals_their() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.FALSE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.FALSE) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), @@ -113,8 +103,9 @@ void should_valid_if_no_additionals_are_allow_and_no_additionals_their() { @Test void should_be_an_applicator_and_an_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.TRUE).build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", JsonValue.EMPTY_JSON_ARRAY)), + JsonSchemas.load(JsonValue.FALSE) ).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); @@ -123,11 +114,9 @@ void should_be_an_applicator_and_an_annotation() { @Test void should_return_propertynames_which_will_be_validated() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .add("additionalProperties", JsonValue.TRUE) - .build() + new AdditionalPropertiesKeyword( + List.of(new StaticAnnotation("properties", Json.createArrayBuilder().add("test").build())), + JsonSchemas.load(JsonValue.TRUE) ) .asAnnotation() .valueFor(Json.createObjectBuilder().add("test", 1).add("foo", 1).build()) @@ -139,16 +128,10 @@ void should_return_propertynames_which_will_be_validated() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("additionalProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ).printOn(new HashMapMedia()), + new AdditionalPropertiesKeyword(List.of(), JsonSchemas.load(JsonValue.EMPTY_JSON_OBJECT)).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("additionalProperties"), anEmptyMap()) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("additionalProperties", AdditionalPropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java new file mode 100644 index 00000000..86bc7560 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AllOfKeywordTest.java @@ -0,0 +1,102 @@ +/* + * 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.hasItems; +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 AllOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = new AllOfKeyword( + Json.createArrayBuilder().add(JsonValue.TRUE).build().stream().map(JsonSchemas::load).toList() + ); + assertThat(items.hasName("allOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("allOf"), hasItems(hasKey("type"), hasKey("minimum"))) + ); + } + + @Test + void should_be_valid_if_all_schemas_applies() { + assertThat( + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createValue(25)), + is(true) + ); + } + + @Test + void should_be_invalid_if_any_schemas_not_apply() { + assertThat( + new AllOfKeyword( + Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("type", "number")) + .add(Json.createObjectBuilder().add("minimum", 18)) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createValue(10)), + is(false) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java new file mode 100644 index 00000000..f7fa853e --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/AnyOfKeywordTest.java @@ -0,0 +1,106 @@ +/* + * 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.hasItem; +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 java.util.List; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class AnyOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = new AnyOfKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); + + assertThat(items.hasName("anyOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new AnyOfKeyword( + List.of( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "allOf", + Json.createArrayBuilder().add(Json.createObjectBuilder().add("type", "number")) + ) + .build() + ) + ) + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("anyOf"), hasItem((hasKey("allOf")))) + ); + } + + @Test + void should_be_valid_if_any_schemas_applies() { + assertThat( + new AnyOfKeyword( + Json.createArrayBuilder() + .add(JsonValue.FALSE) + .add(JsonValue.TRUE) + .add(JsonValue.FALSE) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createValue(25)), + is(true) + ); + } + + @Test + void should_be_invalid_if_no_schemas_apply() { + assertThat( + new AnyOfKeyword( + Json.createArrayBuilder() + .add(JsonValue.FALSE) + .add(JsonValue.FALSE) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createValue(10)), + is(false) + ); + } +} 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..f94a4c35 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,10 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -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.JsonSchemas; 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; @@ -47,19 +43,16 @@ class ContainsKeywordTest { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ).printOn(new HashMapMedia()), + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("contains"), hasEntry(is("type"), is("number"))) ); } @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)); @@ -68,8 +61,8 @@ void should_know_his_name() { @Test void should_be_an_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() + new ContainsKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build()) ).categories(), Matchers.containsInAnyOrder(Keyword.KeywordCategory.ANNOTATION, Keyword.KeywordCategory.APPLICATOR) ); @@ -78,36 +71,17 @@ void should_be_an_applicator_and_annotation() { @Test void should_apply_for_non_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } - @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( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_ARRAY), is(false) @@ -117,9 +91,7 @@ void should_not_apply_to_empty_array_if_non_min_andor_max_is_provided() { @Test void should_apply_if_one_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo( Json.createArrayBuilder() @@ -137,9 +109,7 @@ void should_apply_if_one_item_applies() { @Test void should_apply_if_all_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "string")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "string").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(true) @@ -149,9 +119,7 @@ void should_apply_if_all_item_applies() { @Test void should_not_apply_if_non_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo( Json.createArrayBuilder().add("foo").add(false).add(Json.createArrayBuilder().add("bar")).build() @@ -163,9 +131,7 @@ void should_not_apply_if_non_item_applies() { @Test void should_return_false_for_non_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(JsonValue.EMPTY_JSON_OBJECT), is(JsonValue.FALSE) @@ -175,9 +141,7 @@ void should_return_false_for_non_array() { @Test void should_return_empty_array_if_no_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(Json.createArrayBuilder().add("foo").build()) .asJsonArray(), @@ -188,9 +152,7 @@ void should_return_empty_array_if_no_item_applies() { @Test void should_return_empty_array_for_empty_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor(JsonValue.EMPTY_JSON_ARRAY) .asJsonArray(), @@ -201,9 +163,7 @@ void should_return_empty_array_for_empty_array() { @Test void should_return_matching_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "number")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asAnnotation() .valueFor( Json.createArrayBuilder() @@ -222,20 +182,10 @@ void should_return_matching_items() { @Test void should_return_true_if_all_item_applies() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("contains", Json.createObjectBuilder().add("type", "string")).build() - ) + new ContainsKeyword(JsonSchemas.load(Json.createObjectBuilder().add("type", "string").build())) .asAnnotation() .valueFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(JsonValue.TRUE) ); } - - 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)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java new file mode 100644 index 00000000..e0b1b7b4 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/DependentSchemasKeywordTest.java @@ -0,0 +1,108 @@ +/* + * The MIT License + * + * Copyright 2023 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.anEmptyMap; +import static org.hamcrest.Matchers.hasEntry; +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.keyword.type.NamedJsonSchemas; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import java.util.Map; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class DependentSchemasKeywordTest { + + @Test + void should_know_his_name() { + final Keyword keyword = new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())); + + assertThat(keyword.hasName("dependentSchemas"), is(true)); + assertThat(keyword.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("foo", JsonSchemas.load(JsonValue.TRUE)))).printOn( + new HashMapMedia() + ), + (Matcher) hasEntry(is("dependentSchemas"), hasEntry(is("foo"), anEmptyMap())) + ); + } + + @Test + void should_be_valid_for_non_object() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.FALSE), + is(true) + ); + } + + @Test + void should_be_valid_for_empty_object() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of())) + .asApplicator() + .applyTo(JsonValue.EMPTY_JSON_OBJECT), + is(true) + ); + } + + @Test + void should_be_valid_if_property_not_present() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 1).build()), + is(true) + ); + } + + @Test + void should_be_valid_if_property_is_present_and_schema_apply() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("true", 1).build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_property_is_present_and_schema_not_apply() { + assertThat( + new DependentSchemasKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("true", 1).build()), + is(true) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java index a346d3c0..ef61862d 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ItemsKeywordTest.java @@ -27,22 +27,19 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; +import java.util.List; import org.junit.jupiter.api.Test; class ItemsKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build() - ); - + final Keyword items = new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)); assertThat(items.hasName("items"), is(true)); assertThat(items.hasName("test"), is(false)); } @@ -50,9 +47,7 @@ void should_know_his_name() { @Test void should_be_invalid_if_items_does_not_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", Json.createObjectBuilder().add("type", "number")).build() - ) + new ItemsKeyword(List.of(), JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add("invalid").add(2).build()), is(false) @@ -62,9 +57,7 @@ void should_be_invalid_if_items_does_not_match_schema() { @Test void should_be_valid_if_all_items_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", Json.createObjectBuilder().add("type", "number")).build() - ) + new ItemsKeyword(List.of(), JsonSchemas.load(Json.createObjectBuilder().add("type", "number").build())) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -74,9 +67,7 @@ void should_be_valid_if_all_items_match_schema() { @Test void should_be_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.EMPTY_JSON_OBJECT)).categories(), contains(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -84,7 +75,7 @@ void should_be_applicator_and_annotation() { @Test void should_produces_true_if_is_applied_to_any_instance() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("items", JsonValue.EMPTY_JSON_OBJECT).build()) + new ItemsKeyword(List.of(), JsonSchemas.load(JsonValue.TRUE)) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).build()), is(JsonValue.TRUE) @@ -94,11 +85,9 @@ void should_produces_true_if_is_applied_to_any_instance() { @Test void should_return_false_if_not_applies_to_any_item() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true)) - .add("items", JsonValue.FALSE) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", JsonValue.TRUE)), + JsonSchemas.load(JsonValue.FALSE) ) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).build()), @@ -109,11 +98,9 @@ void should_return_false_if_not_applies_to_any_item() { @Test void should_be_valid_if_invaliditem_is_already_checked_by_prefixItems() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true).add(true)) - .add("items", Json.createObjectBuilder().add("type", "integer")) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", JsonValue.TRUE)), + JsonSchemas.load(Json.createObjectBuilder().add("type", "integer").build()) ) .asApplicator() .applyTo(Json.createArrayBuilder().add("1").add("2").add(1).build()), @@ -124,21 +111,13 @@ void should_be_valid_if_invaliditem_is_already_checked_by_prefixItems() { @Test void should_be_invalid_if_invaliditem_is_not_already_checked_by_prefixItems() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(true)) - .add("items", Json.createObjectBuilder().add("type", "integer")) - .build() + new ItemsKeyword( + List.of(new StaticAnnotation("prefixItems", Json.createValue(0))), + JsonSchemas.load(Json.createObjectBuilder().add("type", "integer").build()) ) .asApplicator() .applyTo(Json.createArrayBuilder().add("1").add("2").add(1).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new SubSchemaKeywordType("items", ItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java new file mode 100644 index 00000000..28cc6849 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/NotKeywordTest.java @@ -0,0 +1,86 @@ +/* + * 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 NotKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = new NotKeyword(JsonSchemas.load(JsonValue.FALSE)); + + assertThat(items.hasName("not"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new NotKeyword( + 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("not"), hasKey("properties")) + ); + } + + @Test + void should_be_valid_if_schema_not_apply() { + assertThat( + new NotKeyword(JsonSchemas.load(JsonValue.FALSE)) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_schema_apply() { + assertThat( + new NotKeyword(JsonSchemas.load(JsonValue.TRUE)) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), + is(false) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.java new file mode 100644 index 00000000..62b9f8da --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/OneOfKeywordTest.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.vocab.applicator; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +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 java.util.List; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +class OneOfKeywordTest { + + @Test + void should_know_his_name() { + final Keyword items = new OneOfKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); + + assertThat(items.hasName("oneOf"), is(true)); + assertThat(items.hasName("test"), is(false)); + } + + @Test + void should_be_printable() { + assertThat( + new OneOfKeyword( + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ).printOn(new HashMapMedia()), + (Matcher) hasEntry(is("oneOf"), hasItem((hasKey("properties")))) + ); + } + + @Test + void should_be_valid_if_exactly_one_schema_applies() { + assertThat( + new OneOfKeyword( + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").build()), + is(true) + ); + } + + @Test + void should_be_invalid_if_none_schema_not_apply() { + assertThat( + new OneOfKeyword( + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", 3).add("bar", "bar").build()), + is(false) + ); + } + + @Test + void should_be_invalid_if_more_than_one_schema_apply() { + assertThat( + new OneOfKeyword( + Json.createArrayBuilder() + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string")) + ) + .add("required", Json.createArrayBuilder().add("foo")) + ) + .add( + Json.createObjectBuilder() + .add( + "properties", + Json.createObjectBuilder().add("bar", Json.createObjectBuilder().add("type", "number")) + ) + .add("required", Json.createArrayBuilder().add("bar")) + ) + .build() + .stream() + .map(JsonSchemas::load) + .toList() + ) + .asApplicator() + .applyTo(Json.createObjectBuilder().add("foo", "foo").add("bar", 33).build()), + is(false) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java index 9d8dc219..f015d7c9 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PatternPropertiesKeywordTest.java @@ -27,18 +27,17 @@ import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonString; import jakarta.json.JsonValue; import java.math.BigDecimal; +import java.util.Map; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -46,9 +45,7 @@ class PatternPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())); assertThat(keyword.hasName("patternProperties"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -57,9 +54,7 @@ void should_know_his_name() { @Test void should_be_an_applicator_and_an_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -67,9 +62,7 @@ void should_be_an_applicator_and_an_annotation() { @Test void should_be_valid_for_non_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build()) - .asApplicator() - .applyTo(JsonValue.FALSE), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.FALSE), is(true) ); } @@ -77,7 +70,7 @@ void should_be_valid_for_non_object() { @Test void should_be_valid_for_empty_object() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("patternProperties", JsonValue.EMPTY_JSON_OBJECT).build()) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of())) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -87,11 +80,7 @@ void should_be_valid_for_empty_object() { @Test void should_be_valid_if_properties_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.TRUE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.TRUE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(true) @@ -101,32 +90,20 @@ void should_be_valid_if_properties_applies_to_his_schema() { @Test void should_be_invalid_if_properties_not_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.FALSE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(false) ); } - /* - Attention can be falky. - JsonObject is a map, so the properties occurred in random order. - This means for this test it could be green without using all assertions. - */ @Test - void should_be_invalid_if_one_schema_doesn_apply() { + void should_be_invalid_if_one_schema_does_not_apply() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "patternProperties", - Json.createObjectBuilder().add("t.st", JsonValue.TRUE).add("t.*", JsonValue.FALSE) - ) - .build() + new PatternPropertiesKeyword( + new NamedJsonSchemas( + Map.of("t.st", JsonSchemas.load(JsonValue.TRUE), "t.*", JsonSchemas.load(JsonValue.FALSE)) + ) ) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), @@ -137,11 +114,7 @@ void should_be_invalid_if_one_schema_doesn_apply() { @Test void should_be_valid_if_properties_not_covered() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("t.st", JsonValue.FALSE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("t.st", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("foo", 1).build()), is(true) @@ -151,11 +124,7 @@ void should_be_valid_if_properties_not_covered() { @Test void should_return_the_matching_property_names() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("f.o", JsonValue.TRUE)) - .build() - ) + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("f.o", JsonSchemas.load(JsonValue.TRUE)))) .asAnnotation() .valueFor( Json.createObjectBuilder() @@ -176,18 +145,10 @@ void should_return_the_matching_property_names() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("patternProperties", Json.createObjectBuilder().add("f.o", JsonValue.TRUE)) - .build() - ).printOn(new HashMapMedia()), + new PatternPropertiesKeyword(new NamedJsonSchemas(Map.of("f.o", JsonSchemas.load(JsonValue.TRUE)))).printOn( + new HashMapMedia() + ), (Matcher) hasEntry(is("patternProperties"), hasEntry(is("f.o"), anEmptyMap())) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType("patternProperties", PatternPropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java index b625575e..9ba9728b 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PrefixItemsKeywordTest.java @@ -31,12 +31,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArraySubSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; 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.junit.jupiter.api.Test; @@ -44,9 +43,7 @@ class PrefixItemsKeywordTest { @Test void should_know_his_name() { - final Keyword items = createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ); + final Keyword items = new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))); assertThat(items.hasName("prefixItems"), is(true)); assertThat(items.hasName("test"), is(false)); @@ -55,9 +52,7 @@ void should_know_his_name() { @Test void should_return_zero_as_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).add(2).build()), is(Json.createValue(0)) @@ -67,10 +62,14 @@ void should_return_zero_as_value() { @Test void should_retrun_true_if_is_applies_to_all_values() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE).add(JsonValue.TRUE)) + new PrefixItemsKeyword( + Json.createArrayBuilder() + .add(JsonValue.TRUE) + .add(JsonValue.TRUE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asAnnotation() .valueFor(Json.createArrayBuilder().add(1).add(2).build()), @@ -81,9 +80,7 @@ void should_retrun_true_if_is_applies_to_all_values() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.FALSE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.FALSE))) .asApplicator() .applyTo(Json.createValue(1)), is(true) @@ -93,9 +90,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_if_first_item_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ) + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).build()), is(true) @@ -105,10 +100,14 @@ void should_be_valid_if_first_item_match_schema() { @Test void should_be_invalid_if_second_item_does_not_match_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE).add(JsonValue.FALSE)) + new PrefixItemsKeyword( + Json.createArrayBuilder() + .add(JsonValue.TRUE) + .add(JsonValue.FALSE) .build() + .stream() + .map(JsonSchemas::load) + .toList() ) .asApplicator() .applyTo(Json.createArrayBuilder().add(1).add(3).build()), @@ -119,9 +118,7 @@ void should_be_invalid_if_second_item_does_not_match_schema() { @Test void should_be_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", JsonValue.EMPTY_JSON_ARRAY).build() - ).categories(), + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -129,16 +126,8 @@ void should_be_applicator_and_annotation() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("prefixItems", Json.createArrayBuilder().add(JsonValue.TRUE)).build() - ).printOn(new HashMapMedia()), + new PrefixItemsKeyword(List.of(JsonSchemas.load(JsonValue.TRUE))).printOn(new HashMapMedia()), (Matcher) hasEntry(is("prefixItems"), contains(anEmptyMap())) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new ArraySubSchemaKeywordType("prefixItems", PrefixItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java index f3e88790..1d18e283 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/PropertiesKeywordTest.java @@ -30,11 +30,10 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import java.util.Map; import org.hamcrest.Matcher; @@ -44,9 +43,7 @@ class PropertiesKeywordTest { @Test void should_be_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword keyword = new PropertiesKeyword(new NamedJsonSchemas(Map.of())); assertThat(keyword.hasName("properties"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -55,9 +52,7 @@ void should_be_know_his_name() { @Test void should_be_an_applicator_and_annotation() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build() - ).categories(), + new PropertiesKeyword(new NamedJsonSchemas(Map.of())).categories(), containsInAnyOrder(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION) ); } @@ -65,9 +60,7 @@ void should_be_an_applicator_and_annotation() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("properties", JsonValue.EMPTY_JSON_OBJECT).build()) - .asApplicator() - .applyTo(JsonValue.EMPTY_JSON_ARRAY), + new PropertiesKeyword(new NamedJsonSchemas(Map.of())).asApplicator().applyTo(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -75,11 +68,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_if_properties_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(true) @@ -89,11 +78,7 @@ void should_be_valid_if_properties_applies_to_his_schema() { @Test void should_be_invalid_if_properties_not_applies_to_his_schema() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(Json.createObjectBuilder().add("test", 1).build()), is(false) @@ -103,11 +88,7 @@ void should_be_invalid_if_properties_not_applies_to_his_schema() { @Test void should_be_valid_for_empty_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.FALSE)) - .build() - ) + new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.FALSE)))) .asApplicator() .applyTo(JsonValue.EMPTY_JSON_OBJECT), is(true) @@ -117,10 +98,10 @@ void should_be_valid_for_empty_objects() { @Test void should_return_all_matched_propertynames() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", true).add("foo", true)) - .build() + new PropertiesKeyword( + new NamedJsonSchemas( + Map.of("test", JsonSchemas.load(JsonValue.TRUE), "foo", JsonSchemas.load(JsonValue.TRUE)) + ) ) .asAnnotation() .valueFor(Json.createObjectBuilder().add("foo", 1).build()) @@ -134,20 +115,10 @@ void should_return_all_matched_propertynames() { @Test void should_be_printable() { assertThat( - ((Map) createKeywordFrom( - Json.createObjectBuilder() - .add("properties", Json.createObjectBuilder().add("test", JsonValue.TRUE)) - .build() - ) + ((Map) new PropertiesKeyword(new NamedJsonSchemas(Map.of("test", JsonSchemas.load(JsonValue.TRUE)))) .printOn(new HashMapMedia()) .get("properties")).get("test"), (Matcher) anEmptyMap() ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType("properties", PropertiesKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java index a853cda2..625e81cf 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/CommentKeywordTest.java @@ -28,21 +28,14 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import org.junit.jupiter.api.Test; class CommentKeywordTest { @Test void should_know_her_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("$comment", Json.createValue("comment")).build() - ); + final Keyword keyword = new CommentKeyword("comment"); assertThat(keyword.hasName("$comment"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -50,17 +43,6 @@ void should_know_her_name() { @Test void should_be_printable() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$comment", Json.createValue("comment")).build()).printOn( - new HashMapMedia() - ), - hasEntry("$comment", "comment") - ); - } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "$comment", CommentKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); + assertThat(new CommentKeyword("comment").printOn(new HashMapMedia()), hasEntry("$comment", "comment")); } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java index 4dd22189..878f9d38 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DefsKeywordTest.java @@ -29,12 +29,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType; +import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; +import java.util.Map; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +39,7 @@ class DefsKeywordTest { @Test void should_know_his_name() { - final Keyword defs = createKeywordFrom( - Json.createObjectBuilder().add("$defs", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword defs = new DefsKeyword(new NamedJsonSchemas(Map.of())); assertThat(defs.hasName("$defs"), is(true)); assertThat(defs.hasName("test"), is(false)); @@ -53,16 +48,8 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$defs", JsonValue.EMPTY_JSON_OBJECT).build()).printOn( - new HashMapMedia() - ), + new DefsKeyword(new NamedJsonSchemas(Map.of())).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$defs"), anEmptyMap()) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NamedJsonSchemaKeywordType(DefsKeyword.NAME, DefsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java index d909a1a9..5c708491 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/DynamicRefKeywordTest.java @@ -28,13 +28,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,37 +38,27 @@ class DynamicRefKeywordTest { @Test void should_create_keyword_with_name() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()).hasName("$dynamicRef"), - is(true) - ); - } - - @Test - void notFinischedYet() { - final Keyword keyword = createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()); + final Keyword keyword = new DynamicRefKeyword(URI.create("test")); assertThat(keyword.hasName("$dynamicRef"), is(true)); assertThat(keyword.hasName("$id"), is(false)); - - assertThat(keyword.asApplicator().applyTo(JsonValue.TRUE), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("$dynamicRef", "test").build()).printOn( - new HashMapMedia() - ), + new DynamicRefKeyword(URI.create("test")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$dynamicRef"), is("test")) ); } - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType( - JsonProvider.provider(), - DynamicRefKeyword.NAME, - s -> new DynamicRefKeyword(URI.create(s)) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); + @Test + void notFinischedYet() { + final Keyword keyword = new DynamicRefKeyword(URI.create("test")); + + assertThat(keyword.hasName("$dynamicRef"), is(true)); + assertThat(keyword.hasName("$id"), is(false)); + + assertThat(keyword.asApplicator().applyTo(JsonValue.TRUE), is(true)); } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java index 0e23f507..3d9cb8fc 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/IdKeywordTest.java @@ -28,12 +28,7 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,7 +37,7 @@ class IdKeywordTest { @Test void should_know_his_name() { - final Keyword id = createKeywordFrom(Json.createObjectBuilder().add("$id", Json.createValue("/test")).build()); + final Keyword id = new IdKeyword(URI.create("/test")); assertThat(id.hasName("$id"), is(true)); assertThat(id.hasName("test"), is(false)); @@ -51,13 +46,7 @@ void should_know_his_name() { @Test void should_retun_his_uri() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$id", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ) - .asIdentifier() - .asUri(), + new IdKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).asIdentifier().asUri(), is(URI.create("https://json-schema.org/draft/2020-12/schema")) ); } @@ -65,18 +54,8 @@ void should_retun_his_uri() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$id", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ).printOn(new HashMapMedia()), + new IdKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$id"), is("https://json-schema.org/draft/2020-12/schema")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "$id", s -> new IdKeyword(URI.create(s))).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java index dc34c53c..0a1e9baf 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTest.java @@ -26,55 +26,45 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; import io.github.sebastiantoepfer.jsonschema.JsonSchema; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; +import java.io.IOException; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; class RefKeywordTest { - @Test - void should_be_not_createable_from_non_string() { - final RefKeywordType keywordType = new RefKeywordType(JsonProvider.provider()); - final JsonSchema schema = new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$ref", JsonValue.TRUE).build()); - assertThrows(IllegalArgumentException.class, () -> keywordType.createKeyword(schema)); - } - @Test void should_know_his_name() { - final Keyword ref = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory().create(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()) + final Keyword ref = new RefKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), + URI.create("#"), + new SchemaRegistry.DefaultSchemaRegistry() ); - assertThat(ref.hasName("$ref"), is(true)); assertThat(ref.hasName("test"), is(false)); } @Test void should_use_local_referenced_schema_for_validation() { - final Keyword keyword = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$defs", - Json.createObjectBuilder() - .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) - ) - .add("$ref", Json.createValue("#/$defs/positiveInteger")) - .build() - ) + final Keyword keyword = new RefKeyword( + JsonSchemas.load( + Json.createObjectBuilder() + .add( + "$defs", + Json.createObjectBuilder() + .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) + ) + .add("$ref", Json.createValue("#/$defs/positiveInteger")) + .build() + ), + URI.create("#/$defs/positiveInteger"), + new SchemaRegistry.DefaultSchemaRegistry() ); assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); @@ -83,42 +73,44 @@ void should_use_local_referenced_schema_for_validation() { @Test void should_use_remote_referenced_schema_for_validation() { - final Keyword keyword = new RefKeywordType(JsonProvider.provider()).createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$defs", + final Keyword keyword = new RefKeyword( + JsonSchemas.load( + Json.createObjectBuilder().add("$ref", Json.createValue("https://schema.org/byMonth")).build() + ), + URI.create("https://schema.org/byMonth"), + new SchemaRegistry() { + @Override + public JsonSchema schemaForUrl(final URI uri) throws IOException { + if (uri.equals(URI.create("https://schema.org/byMonth"))) { + //hen egg issue -> we need more than core :( + return JsonSchemas.load( Json.createObjectBuilder() - .add("positiveInteger", Json.createObjectBuilder().add("type", "integer")) - ) - .add("$ref", Json.createValue("#/$defs/positiveInteger")) - .build() - ) + .add("type", "integer") + .add("minimum", 1) + .add("maximum", 12) + .build() + ); + } else { + throw new IOException("can not retrieve remote schema!"); + } + } + } ); - assertThat(keyword.asApplicator().applyTo(Json.createValue(1L)), is(true)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(12L)), is(true)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(0L)), is(false)); + assertThat(keyword.asApplicator().applyTo(Json.createValue(13L)), is(false)); } @Test void should_be_printable() { assertThat( - new RefKeywordType(JsonProvider.provider()) - .createKeyword( - new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()) - ) - .printOn(new HashMapMedia()), + new RefKeyword( + JsonSchemas.load(Json.createObjectBuilder().add("$ref", Json.createValue("#")).build()), + URI.create("#"), + new SchemaRegistry.DefaultSchemaRegistry() + ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$ref"), is("#")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - final JsonSchema schema = new DefaultJsonSchemaFactory().create(json); - return new StringKeywordType( - JsonProvider.provider(), - "$ref", - s -> new RefKeyword(schema, URI.create(s)) - ).createKeyword(schema); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java index ab7ed341..5750e79d 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/RefKeywordTypeTest.java @@ -25,8 +25,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import io.github.sebastiantoepfer.jsonschema.JsonSchema; +import io.github.sebastiantoepfer.jsonschema.JsonSchemas; +import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType; +import jakarta.json.Json; +import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import org.junit.jupiter.api.Test; @@ -40,4 +48,24 @@ void should_know_the_keywords_name() { assertThat(refKeywordType.hasName("ref"), is(false)); assertThat(refKeywordType.hasName("test"), is(false)); } + + @Test + void should_be_not_createable_from_non_string() { + final RefKeywordType keywordType = new RefKeywordType(JsonProvider.provider()); + final JsonSchema schema = new DefaultJsonSchemaFactory() + .create(Json.createObjectBuilder().add("$ref", JsonValue.TRUE).build()); + assertThrows(IllegalArgumentException.class, () -> keywordType.createKeyword(schema)); + } + + @Test + void should_create_refkeyword() { + assertThat( + new RefKeywordType(JsonProvider.provider()).createKeyword( + JsonSchemas.load( + Json.createObjectBuilder().add("$ref", Json.createValue("#/$defs/positiveInteger")).build() + ) + ), + is(not(nullValue())) + ); + } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java index e2ff6c7f..cc3eb4b5 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaKeywordTest.java @@ -28,12 +28,7 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.spi.JsonProvider; import java.net.URI; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +37,7 @@ class SchemaKeywordTest { @Test void should_know_his_name() { - final Keyword schema = createKeywordFrom( - Json.createObjectBuilder().add("$schema", Json.createValue("/test")).build() - ); + final Keyword schema = new SchemaKeyword(URI.create("/test")); assertThat(schema.hasName("$schema"), is(true)); assertThat(schema.hasName("test"), is(false)); @@ -53,13 +46,7 @@ void should_know_his_name() { @Test void should_retun_his_uri() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$schema", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ) - .asIdentifier() - .asUri(), + new SchemaKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).asIdentifier().asUri(), is(URI.create("https://json-schema.org/draft/2020-12/schema")) ); } @@ -67,20 +54,8 @@ void should_retun_his_uri() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("$schema", Json.createValue("https://json-schema.org/draft/2020-12/schema")) - .build() - ).printOn(new HashMapMedia()), + new SchemaKeyword(URI.create("https://json-schema.org/draft/2020-12/schema")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("$schema"), is("https://json-schema.org/draft/2020-12/schema")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType( - JsonProvider.provider(), - "$schema", - s -> new SchemaKeyword(URI.create(s)) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java new file mode 100644 index 00000000..9723eec0 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/SchemaRegistryTest.java @@ -0,0 +1,42 @@ +/* + * 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.core; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import jakarta.json.JsonValue; +import java.net.URI; +import org.junit.jupiter.api.Test; + +class SchemaRegistryTest { + + @Test + void should_return_false_schema() throws Exception { + assertThat( + new SchemaRegistry.DefaultSchemaRegistry().schemaForUrl(URI.create("test")).getValueType(), + is(JsonValue.ValueType.FALSE) + ); + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java new file mode 100644 index 00000000..e0356c83 --- /dev/null +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTest.java @@ -0,0 +1,190 @@ +/* + * The MIT License + * + * Copyright 2023 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.core; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; + +import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.LazyVocabularies; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary; +import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import java.net.URI; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.jupiter.api.Test; + +class VocabularyKeywordTest { + + @Test + void should_created_keyword_should_know_his_name() { + final Keyword vocabulary = new VocabularyKeyword(JsonValue.EMPTY_JSON_OBJECT, Stream::empty); + + assertThat(vocabulary.hasName("$vocabulary"), is(true)); + assertThat(vocabulary.hasName("$id"), is(false)); + } + + @Test + void should_create_definitions() { + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build(), + Stream::empty + ) + .definitions() + .toList(), + containsInAnyOrder( + new VocabularyDefinitionMatcher(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true), + new VocabularyDefinitionMatcher(URI.create("http://openapi.org/test"), false) + ) + ); + } + + @Test + void should_be_printable() { + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build(), + Stream::empty + ).printOn(new HashMapMedia()), + (Matcher) hasEntry( + is("$vocabulary"), + allOf( + hasEntry("https://json-schema.org/draft/2020-12/vocab/core", true), + hasEntry("http://openapi.org/test", false) + ) + ) + ); + } + + @Test + void should_not_lazy_load_offical_vocabs() { + //security and peformance. it should not be possible to override keywords define by this module! + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", true).build(), + Stream::empty + ) + .definitions() + .map(VocabularyDefinition::findVocabulary) + .flatMap(Optional::stream) + .map(Vocabulary::id) + .toList(), + containsInAnyOrder(URI.create("https://json-schema.org/draft/2020-12/vocab/core")) + ); + } + + @Test + void should_provide_vocab_which_know_they_id() { + final VocabularyDefinition vocabDef = new VocabularyKeyword( + Json.createObjectBuilder().add("https://json-schema.org/draft/2020-12/vocab/core", true).build(), + Stream::empty + ) + .definitions() + .findFirst() + .orElseThrow(); + + assertThat(vocabDef.hasid(URI.create("https://json-schema.org/draft/2020-12/vocab/core")), is(true)); + assertThat(vocabDef.hasid(URI.create("http://openapi.org/test")), is(false)); + } + + @Test + void should_lazy_load_non_offical_vocabs() { + assertThat( + new VocabularyKeyword( + Json.createObjectBuilder() + .add("https://json-schema.org/draft/2020-12/vocab/core", true) + .add("http://openapi.org/test", false) + .build(), + () -> + Stream.of( + new LazyVocabularies() { + @Override + public Optional loadVocabularyWithId(final URI id) { + return Optional.of(new ListVocabulary(id, List.of())); + } + } + ) + ) + .definitions() + .map(VocabularyDefinition::findVocabulary) + .flatMap(Optional::stream) + .map(Vocabulary::id) + .toList(), + containsInAnyOrder( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + URI.create("http://openapi.org/test") + ) + ); + } + + private static class VocabularyDefinitionMatcher extends TypeSafeMatcher { + + private final URI id; + private final Matcher required; + + public VocabularyDefinitionMatcher(final URI id) { + this(id, null); + } + + public VocabularyDefinitionMatcher(final URI id, final Boolean required) { + super(VocabularyDefinition.class); + this.id = Objects.requireNonNull(id); + this.required = required == null ? either(is(true)).or(is(false)) : is(required); + } + + @Override + protected boolean matchesSafely(final VocabularyDefinition item) { + return item.hasid(id) && required.matches(item.isRequired()); + } + + @Override + public void describeTo(final Description description) { + description + .appendText("Vocabulary definition with id: ") + .appendValue(id) + .appendText(" and required: ") + .appendDescriptionOf(required); + } + } +} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java deleted file mode 100644 index 3ee97e25..00000000 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/core/VocabularyKeywordTypeTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * The MIT License - * - * Copyright 2023 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.core; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; - -import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinition; -import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.VocabularyDefinitions; -import jakarta.json.Json; -import jakarta.json.JsonValue; -import java.net.URI; -import org.hamcrest.Matcher; -import org.junit.jupiter.api.Test; - -class VocabularyKeywordTypeTest { - - @Test - void should_created_keyword_should_know_his_name() { - final Keyword vocabulary = new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create(Json.createObjectBuilder().add("$vocabulary", JsonValue.EMPTY_JSON_OBJECT).build()) - ); - - assertThat(vocabulary.hasName("$vocabulary"), is(true)); - assertThat(vocabulary.hasName("$id"), is(false)); - } - - @Test - void should_create_definitions() { - assertThat( - ((VocabularyDefinitions) new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$vocabulary", - Json.createObjectBuilder() - .add("https://json-schema.org/draft/2020-12/vocab/core", true) - .add("http://openapi.org/test", false) - ) - .build() - ) - )).definitions() - .toList(), - containsInAnyOrder( - new VocabularyDefinition(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true), - new VocabularyDefinition(URI.create("http://openapi.org/test"), false) - ) - ); - } - - @Test - void should_be_printable() { - assertThat( - new VocabularyKeywordType() - .createKeyword( - new DefaultJsonSchemaFactory() - .create( - Json.createObjectBuilder() - .add( - "$vocabulary", - Json.createObjectBuilder() - .add("https://json-schema.org/draft/2020-12/vocab/core", true) - .add("http://openapi.org/test", false) - ) - .build() - ) - ) - .printOn(new HashMapMedia()), - (Matcher) hasEntry( - is("$vocabulary"), - allOf( - hasEntry("https://json-schema.org/draft/2020-12/vocab/core", true), - hasEntry("http://openapi.org/test", false) - ) - ) - ); - } -} diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java index 15edcad9..63d7f2e9 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ConstKeywordTest.java @@ -29,11 +29,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AnyKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import org.hamcrest.Matcher; @@ -43,9 +40,7 @@ class ConstKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("const", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword enumKeyword = new ConstKeyword(JsonProvider.provider(), JsonValue.EMPTY_JSON_ARRAY); assertThat(enumKeyword.hasName("const"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -54,7 +49,7 @@ void should_know_his_name() { @Test void should_be_valid_for_same_string_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue("hello")).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue("hello")) .asAssertion() .isValidFor(Json.createValue("hello")), is(true) @@ -64,7 +59,7 @@ void should_be_valid_for_same_string_value() { @Test void should_be_invalid_for_different_string_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue("hello")).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue("hello")) .asAssertion() .isValidFor(Json.createValue("world")), is(false) @@ -74,7 +69,7 @@ void should_be_invalid_for_different_string_value() { @Test void should_be_valid_for_same_number_value() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue(3.14159)).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(3.14159)) .asAssertion() .isValidFor(Json.createValue(3.14159)), is(true) @@ -84,7 +79,7 @@ void should_be_valid_for_same_number_value() { @Test void should_be_invalid_for_value_with_different_type() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", Json.createValue(3.14159)).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(3.14159)) .asAssertion() .isValidFor(Json.createValue("pi")), is(false) @@ -94,10 +89,9 @@ void should_be_invalid_for_value_with_different_type() { @Test void should_be_valid_for_exact_object_structure() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("const", Json.createObjectBuilder().add("name", "John Doe").add("age", 31)) - .build() + new ConstKeyword( + JsonProvider.provider(), + Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build()), @@ -108,10 +102,9 @@ void should_be_valid_for_exact_object_structure() { @Test void should_be_invalid_for_not_exact_object_structure() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("const", Json.createObjectBuilder().add("name", "John Doe").add("age", 31)) - .build() + new ConstKeyword( + JsonProvider.provider(), + Json.createObjectBuilder().add("name", "John Doe").add("age", 31).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "Robert").add("age", 31).build()), @@ -122,7 +115,7 @@ void should_be_invalid_for_not_exact_object_structure() { @Test void should_be_valid_for_decimal_without_scale_if_number_is_valid() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("const", 1).build()) + new ConstKeyword(JsonProvider.provider(), Json.createValue(1)) .asAssertion() .isValidFor(Json.createValue(1.0)), is(true) @@ -132,16 +125,11 @@ void should_be_valid_for_decimal_without_scale_if_number_is_valid() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("const", Json.createArrayBuilder().add("TEST").add("VALID")).build() + new ConstKeyword( + JsonProvider.provider(), + Json.createArrayBuilder().add("TEST").add("VALID").build() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("const"), containsInAnyOrder("TEST", "VALID")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AnyKeywordType(JsonProvider.provider(), "const", ConstKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java index 00a0e529..0a361c80 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/DependentRequiredKeywordTest.java @@ -30,8 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ObjectKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; import jakarta.json.JsonObject; @@ -62,9 +60,7 @@ void should_not_be_createable_with_object_contains_only_non_string_array_propert @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("dependentRequired", JsonValue.EMPTY_JSON_OBJECT).build() - ); + final Keyword enumKeyword = new DependentRequiredKeyword(JsonValue.EMPTY_JSON_OBJECT); assertThat(enumKeyword.hasName("dependentRequired"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -73,13 +69,8 @@ void should_know_his_name() { @Test void should_be_valid_if_both_properties_available() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor( @@ -92,13 +83,8 @@ void should_be_valid_if_both_properties_available() { @Test void should_be_valid_if_required_properties_is_missing() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John").add("license", "XYZ123").build()), @@ -109,13 +95,8 @@ void should_be_valid_if_required_properties_is_missing() { @Test void should_be_valid_if_both_properties_are_missing() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ) .asAssertion() .isValidFor(Json.createObjectBuilder().add("name", "John").build()), @@ -126,21 +107,10 @@ void should_be_valid_if_both_properties_are_missing() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add( - "dependentRequired", - Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")) - ) - .build() + new DependentRequiredKeyword( + Json.createObjectBuilder().add("license", Json.createArrayBuilder().add("age")).build() ).printOn(new HashMapMedia()), (Matcher) hasEntry(is("dependentRequired"), hasEntry(is("license"), contains("age"))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new ObjectKeywordType("dependentRequired", DependentRequiredKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java index cb7c6890..440da115 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/EnumKeywordTest.java @@ -29,11 +29,8 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +39,7 @@ class EnumKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom( - Json.createObjectBuilder().add("enum", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword enumKeyword = new EnumKeyword(JsonValue.EMPTY_JSON_ARRAY); assertThat(enumKeyword.hasName("enum"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -53,9 +48,7 @@ void should_know_his_name() { @Test void should_valid_for_string_value_which_is_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ) + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()) .asAssertion() .isValidFor(Json.createValue("TEST")), is(true) @@ -65,9 +58,7 @@ void should_valid_for_string_value_which_is_in_array() { @Test void should_be_invalid_for_number_which_is_not_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ) + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()) .asAssertion() .isValidFor(Json.createValue(2)), is(false) @@ -77,9 +68,7 @@ void should_be_invalid_for_number_which_is_not_in_array() { @Test void should_be_valid_for_decimal_without_scale_if_number_is_valid() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add(1)).build()) - .asAssertion() - .isValidFor(Json.createValue(1.0)), + new EnumKeyword(Json.createArrayBuilder().add(1).build()).asAssertion().isValidFor(Json.createValue(1.0)), is(true) ); } @@ -87,16 +76,8 @@ void should_be_valid_for_decimal_without_scale_if_number_is_valid() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("enum", Json.createArrayBuilder().add("TEST").add("VALID")).build() - ).printOn(new HashMapMedia()), + new EnumKeyword(Json.createArrayBuilder().add("TEST").add("VALID").build()).printOn(new HashMapMedia()), (Matcher) hasEntry(is("enum"), containsInAnyOrder("TEST", "VALID")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new ArrayKeywordType("enum", EnumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java index 9242c0a2..b1b51d47 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMaximumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class ExclusiveMaximumKeywordTest { @Test void should_know_his_name() { - final Keyword maximum = createKeywordFrom( - Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(1)).build() - ); + final Keyword maximum = new ExclusiveMaximumKeyword(BigDecimal.valueOf(1)); assertThat(maximum.hasName("exclusiveMaximum"), is(true)); assertThat(maximum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(11)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(11)), is(false) ); } @@ -74,9 +64,7 @@ void should_be_invalid_for_greater_numbers() { @Test void should_be_invalid_for_equals_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(10)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(10)), is(false) ); } @@ -84,9 +72,7 @@ void should_be_invalid_for_equals_numbers() { @Test void shhould_be_valid_for_smaller_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(9)), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(9)), is(true) ); } @@ -94,18 +80,8 @@ void shhould_be_valid_for_smaller_numbers() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMaximum", Json.createValue(10)).build()).printOn( - new HashMapMedia() - ), + new ExclusiveMaximumKeyword(BigDecimal.valueOf(10)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("exclusiveMaximum"), is(BigDecimal.valueOf(10))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType( - JsonProvider.provider(), - "exclusiveMaximum", - ExclusiveMaximumKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java index c6ca0db8..d19d4185 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/ExclusiveMinimumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class ExclusiveMinimumKeywordTest { @Test void should_know_his_name() { - final Keyword minimum = createKeywordFrom( - Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(1)).build() - ); + final Keyword minimum = new ExclusiveMinimumKeyword(BigDecimal.valueOf(1)); assertThat(minimum.hasName("exclusiveMinimum"), is(true)); assertThat(minimum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_smaller_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(-1)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(-1)), is(false) ); } @@ -74,9 +64,7 @@ void should_be_invalid_for_smaller_numbers() { @Test void should_be_invalid_for_equals_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(0)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(0)), is(false) ); } @@ -84,9 +72,7 @@ void should_be_invalid_for_equals_numbers() { @Test void shhould_be_valid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(1)), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(1)), is(true) ); } @@ -94,18 +80,8 @@ void shhould_be_valid_for_greater_numbers() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("exclusiveMinimum", Json.createValue(0)).build()).printOn( - new HashMapMedia() - ), + new ExclusiveMinimumKeyword(BigDecimal.valueOf(0)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("exclusiveMinimum"), is(BigDecimal.valueOf(0))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType( - JsonProvider.provider(), - "exclusiveMinimum", - ExclusiveMinimumKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } 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..4b1c889b 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 @@ -28,16 +28,12 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -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; import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; 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 +41,7 @@ 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(), BigInteger.ONE); assertThat(enumKeyword.hasName("maxContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); } @@ -53,7 +49,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()).printOn(new HashMapMedia()), + new MaxContainsKeyword(List.of(), BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxContains"), is(BigInteger.valueOf(2))) ); } @@ -61,31 +57,19 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxContains", 2).build()) + new MaxContainsKeyword(List.of(), BigInteger.valueOf(2)) .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( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).build()), @@ -96,11 +80,9 @@ void should_be_valid_if_contains_applies_to_exact_count() { @Test void should_be_valid_if_contains_applies_to_less_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(2).add(3).build()), @@ -111,11 +93,9 @@ void should_be_valid_if_contains_applies_to_less_items() { @Test void should_be_valid_for_empty_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", JsonValue.EMPTY_JSON_ARRAY)), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_ARRAY), @@ -126,11 +106,9 @@ void should_be_valid_for_empty_arrays() { @Test void should_be_invalid_if_contains_applies_to_more_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() + new MaxContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).add(3).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).add("baz").build()), @@ -141,12 +119,7 @@ void should_be_invalid_if_contains_applies_to_more_items() { @Test void should_be_valid_if_contains_applies_to_all_and_less_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").build()), is(true) @@ -156,12 +129,7 @@ void should_be_valid_if_contains_applies_to_all_and_less_items_in_array() { @Test void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").build()), is(true) @@ -171,28 +139,10 @@ void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() @Test void should_be_invalid_if_contains_applies_to_all_and_more_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("maxContains", 2) - .build() - ) + new MaxContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectsKeywordType( - "maxContains", - "contains", - (a, s) -> - new IntegerKeywordType( - JsonProvider.provider(), - "maxContains", - value -> new MaxContainsKeyword(a, value) - ).createKeyword(s) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java index 182a3360..de931481 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MaxItemsKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("maxItems", Json.createValue(1)).build() - ); + final Keyword keyword = new MaxItemsKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("maxItems"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MaxItemsKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,7 +56,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_for_array_with_same_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -74,7 +66,7 @@ void should_be_valid_for_array_with_same_size() { @Test void should_be_valid_for_array_with_smaller_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).build()), is(true) @@ -84,7 +76,7 @@ void should_be_valid_for_array_with_smaller_size() { @Test void should_be_invalid_for_array_with_greather_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()) + new MaxItemsKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).add(3).build()), is(false) @@ -94,16 +86,8 @@ void should_be_invalid_for_array_with_greather_size() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxItems", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MaxItemsKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxItems"), is(BigInteger.valueOf(2))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "maxItems", MaxItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java index 886a2ef8..b995e9bb 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxLengthKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MaxLengthKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom(Json.createObjectBuilder().add("maxLength", 1).build()); + final Keyword keyword = new MaxLengthKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("test"), is(false)); assertThat(keyword.hasName("maxLength"), is(true)); @@ -52,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_string_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -62,9 +56,7 @@ void should_be_valid_for_non_string_values() { @Test void should_be_invalid_for_longer_string() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("1234")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("1234")), is(false) ); } @@ -72,9 +64,7 @@ void should_be_invalid_for_longer_string() { @Test void should_be_valid_for_string_with_length_is_equal() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("12")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("12")), is(true) ); } @@ -82,9 +72,7 @@ void should_be_valid_for_string_with_length_is_equal() { @Test void should_ne_valid_for_string_with_is_shorter() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("1")), + new MaxLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("1")), is(true) ); } @@ -92,16 +80,8 @@ void should_ne_valid_for_string_with_is_shorter() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxLength", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MaxLengthKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxLength"), is(BigInteger.valueOf(2L))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "maxLength", MaxLengthKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java index b82c2400..aad33a35 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaxPropertiesKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MaxPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()); + final Keyword enumKeyword = new MaxPropertiesKeyword(BigInteger.valueOf(2)); assertThat(enumKeyword.hasName("maxProperties"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -52,7 +48,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()).printOn(new HashMapMedia()), + new MaxPropertiesKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maxProperties"), is(BigInteger.valueOf(2))) ); } @@ -60,9 +56,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MaxPropertiesKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -70,7 +64,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_for_less_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).build()), is(true) @@ -80,7 +74,7 @@ void should_be_valid_for_less_properties() { @Test void should_be_valid_exact_properties_count() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).add("bar", "hi").build()), is(true) @@ -90,18 +84,10 @@ void should_be_valid_exact_properties_count() { @Test void should_be_invalid_for_more_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maxProperties", 2).build()) + new MaxPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 3).add("bar", "hi").add("baz", true).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType( - JsonProvider.provider(), - "maxProperties", - MaxPropertiesKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java index 10bbd699..9e57fc3f 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MaximumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MaximumKeywordTest { @Test void should_know_his_name() { - final Keyword maximum = createKeywordFrom( - Json.createObjectBuilder().add("maximum", Json.createValue(1)).build() - ); + final Keyword maximum = new MaximumKeyword(BigDecimal.valueOf(1)); assertThat(maximum.hasName("maximum"), is(true)); assertThat(maximum.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MaximumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,46 +56,26 @@ void should_be_valid_for_non_number_values() { @Test void should_be_invalid_for_greater_numbers() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(11)), + new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(11)), is(false) ); } @Test void should_be_valid_for_equals_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(10)), - is(true) - ); + assertThat(new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(10)), is(true)); } @Test void shhould_be_valid_for_smaller_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(Json.createValue(9)), - is(true) - ); + assertThat(new MaximumKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(Json.createValue(9)), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("maximum", Json.createValue(10)).build()).printOn( - new HashMapMedia() - ), + new MaximumKeyword(BigDecimal.valueOf(10)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("maximum"), is(BigDecimal.valueOf(10))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "maximum", MaximumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } 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..aaf6f2e0 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 @@ -28,16 +28,12 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -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; import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation; import jakarta.json.Json; -import jakarta.json.JsonObject; 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 +41,7 @@ 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(), BigInteger.ONE); assertThat(enumKeyword.hasName("minContains"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -54,7 +50,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()).printOn(new HashMapMedia()), + new MinContainsKeyword(List.of(), BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minContains"), is(BigInteger.valueOf(2))) ); } @@ -62,31 +58,19 @@ void should_be_printable() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minContains", 2).build()) + new MinContainsKeyword(List.of(), BigInteger.valueOf(2)) .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( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(1).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add(1).build()), @@ -97,11 +81,9 @@ void should_be_valid_if_contains_applies_to_exact_count() { @Test void should_be_valid_if_contains_applies_to_more_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).add(3).add(4).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(2).add(3).add("bar").add("baz").build()), @@ -112,11 +94,9 @@ void should_be_valid_if_contains_applies_to_more_items() { @Test void should_be_valid_for_empty_arrays() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("const", 1)) - .add("minContains", 0) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().build())), + BigInteger.valueOf(0) ) .asAssertion() .isValidFor(JsonValue.EMPTY_JSON_ARRAY), @@ -127,11 +107,9 @@ void should_be_valid_for_empty_arrays() { @Test void should_be_invalid_if_contains_applies_to_less_items() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() + new MinContainsKeyword( + List.of(new StaticAnnotation("contains", Json.createArrayBuilder().add(0).build())), + BigInteger.valueOf(2) ) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add(1).build()), @@ -142,12 +120,7 @@ void should_be_invalid_if_contains_applies_to_less_items() { @Test void should_be_valid_if_contains_applies_to_all_and_more_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").add("baz").build()), is(true) @@ -157,12 +130,7 @@ void should_be_valid_if_contains_applies_to_all_and_more_items_in_array() { @Test void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").add("bar").build()), is(true) @@ -172,28 +140,10 @@ void should_be_valid_if_contains_applies_to_all_and_exact_items_count_in_array() @Test void should_be_invalid_if_contains_applies_to_all_and_less_items_in_array() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("contains", Json.createObjectBuilder().add("type", "string")) - .add("minContains", 2) - .build() - ) + new MinContainsKeyword(List.of(new StaticAnnotation("contains", JsonValue.TRUE)), BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createArrayBuilder().add("foo").build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new AffectsKeywordType( - "minContains", - "contains", - (a, s) -> - new IntegerKeywordType( - JsonProvider.provider(), - "minContains", - value -> new MinContainsKeyword(a, value) - ).createKeyword(s) - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java index 1af26aa0..e25711f8 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinItemsKeywordTest { @Test void should_be_know_his_name() { - final Keyword minItems = createKeywordFrom( - Json.createObjectBuilder().add("minItems", Json.createValue(1)).build() - ); + final Keyword minItems = new MinItemsKeyword(BigInteger.valueOf(1)); assertThat(minItems.hasName("minItems"), is(true)); assertThat(minItems.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_be_know_his_name() { @Test void should_be_valid_for_non_arrays() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MinItemsKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,7 +56,7 @@ void should_be_valid_for_non_arrays() { @Test void should_be_valid_for_arrays_with_equals_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) + new MinItemsKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).build()), is(true) @@ -74,7 +66,7 @@ void should_be_valid_for_arrays_with_equals_size() { @Test void should_be_valid_for_arrays_with_greater_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) + new MinItemsKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createArrayBuilder().add(1).add(2).build()), is(true) @@ -84,9 +76,7 @@ void should_be_valid_for_arrays_with_greater_size() { @Test void should_be_invalid_for_arrays_with_smaller_size() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().build()), + new MinItemsKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(Json.createArrayBuilder().build()), is(false) ); } @@ -94,16 +84,8 @@ void should_be_invalid_for_arrays_with_smaller_size() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minItems", Json.createValue(1)).build()).printOn( - new HashMapMedia() - ), + new MinItemsKeyword(BigInteger.valueOf(1)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minItems"), is(BigInteger.valueOf(1))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "minItems", MinItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java index 798ca931..d5b21c30 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinLengthKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinLengthKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("minLength", Json.createValue(1)).build() - ); + final Keyword keyword = new MinLengthKeyword(BigInteger.valueOf(1)); assertThat(keyword.hasName("test"), is(false)); assertThat(keyword.hasName("minLength"), is(true)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_invalid_with_shorter_string() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("A")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("A")), is(false) ); } @@ -64,9 +56,7 @@ void should_be_invalid_with_shorter_string() { @Test void should_be_valid_with_string_with_equal_length() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("AB")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("AB")), is(true) ); } @@ -74,9 +64,7 @@ void should_be_valid_with_string_with_equal_length() { @Test void should_be_valid_with_string_that_is_longer() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue("ABC")), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(Json.createValue("ABC")), is(true) ); } @@ -84,9 +72,7 @@ void should_be_valid_with_string_that_is_longer() { @Test void should_be_valid_for_non_string_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MinLengthKeyword(BigInteger.valueOf(2)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -94,16 +80,8 @@ void should_be_valid_for_non_string_values() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minLength", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MinLengthKeyword(BigInteger.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minLength"), is(BigInteger.valueOf(2L))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType(JsonProvider.provider(), "minLength", MinLengthKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java index 9f7597af..1af6fa00 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinPropertiesKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.IntegerKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigInteger; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,7 +39,7 @@ class MinPropertiesKeywordTest { @Test void should_know_his_name() { - final Keyword enumKeyword = createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()); + final Keyword enumKeyword = new MinPropertiesKeyword(BigInteger.valueOf(1)); assertThat(enumKeyword.hasName("minProperties"), is(true)); assertThat(enumKeyword.hasName("test"), is(false)); @@ -52,7 +48,7 @@ void should_know_his_name() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()).printOn(new HashMapMedia()), + new MinPropertiesKeyword(BigInteger.valueOf(1)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minProperties"), is(BigInteger.valueOf(1))) ); } @@ -60,9 +56,7 @@ void should_be_printable() { @Test void should_be_valid_for_non_objects() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new MinPropertiesKeyword(BigInteger.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -70,7 +64,7 @@ void should_be_valid_for_non_objects() { @Test void should_be_valid_for_excat_properties_count() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) + new MinPropertiesKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).build()), is(true) @@ -80,7 +74,7 @@ void should_be_valid_for_excat_properties_count() { @Test void should_be_valid_for_more_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 1).build()) + new MinPropertiesKeyword(BigInteger.valueOf(1)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).add("bar", "hi").build()), is(true) @@ -90,18 +84,10 @@ void should_be_valid_for_more_properties() { @Test void should_be_invalid_for_less_properties() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minProperties", 2).build()) + new MinPropertiesKeyword(BigInteger.valueOf(2)) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", 1).build()), is(false) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new IntegerKeywordType( - JsonProvider.provider(), - "minProperties", - MinPropertiesKeyword::new - ).createKeyword(new DefaultJsonSchemaFactory().create(json)); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java index 456a9784..b40b6f82 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MinimumKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MinimumKeywordTest { @Test void should_know_his_name() { - final Keyword minimum = createKeywordFrom( - Json.createObjectBuilder().add("minimum", Json.createValue(1)).build() - ); + final Keyword minimum = new MinimumKeyword(BigDecimal.valueOf(1)); assertThat(minimum.hasName("minimum"), is(true)); assertThat(minimum.hasName("test"), is(false)); @@ -54,56 +48,31 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(1)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MinimumKeyword(BigDecimal.valueOf(1)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @Test void should_be_invalid_for_smaller_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(-1)), - is(false) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(-1)), is(false)); } @Test void should_be_valid_for_equals_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(0)), - is(true) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(0)), is(true)); } @Test void shhould_be_valid_for_greater_numbers() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()) - .asAssertion() - .isValidFor(Json.createValue(1)), - is(true) - ); + assertThat(new MinimumKeyword(BigDecimal.valueOf(0)).asAssertion().isValidFor(Json.createValue(1)), is(true)); } @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("minimum", Json.createValue(0)).build()).printOn( - new HashMapMedia() - ), + new MinimumKeyword(BigDecimal.valueOf(0)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("minimum"), is(BigDecimal.valueOf(0))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "minimum", MinimumKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java index ee677364..9f3a405c 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/MultipleOfKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NumberKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -43,9 +39,7 @@ class MultipleOfKeywordTest { @Test void should_know_his_name() { - final Keyword multipleOf = createKeywordFrom( - Json.createObjectBuilder().add("multipleOf", Json.createValue(10)).build() - ); + final Keyword multipleOf = new MultipleOfKeyword(BigDecimal.valueOf(10)); assertThat(multipleOf.hasName("multipleOf"), is(true)); assertThat(multipleOf.hasName("test"), is(false)); @@ -54,9 +48,7 @@ void should_know_his_name() { @Test void should_be_valid_for_non_number_values() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(10)).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), + new MultipleOfKeyword(BigDecimal.valueOf(10)).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true) ); } @@ -64,9 +56,7 @@ void should_be_valid_for_non_number_values() { @Test void should_be_valid_for_a_multipleOf() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(1.5)).build()) - .asAssertion() - .isValidFor(Json.createValue(4.5)), + new MultipleOfKeyword(BigDecimal.valueOf(1.5)).asAssertion().isValidFor(Json.createValue(4.5)), is(true) ); } @@ -74,9 +64,7 @@ void should_be_valid_for_a_multipleOf() { @Test void should_be_invalid_for_non_multipleOf() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(2)).build()) - .asAssertion() - .isValidFor(Json.createValue(7)), + new MultipleOfKeyword(BigDecimal.valueOf(2)).asAssertion().isValidFor(Json.createValue(7)), is(false) ); } @@ -84,11 +72,7 @@ void should_be_invalid_for_non_multipleOf() { @Test void should_be_valid_for_any_int_if_multipleOf_is_1en8() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("multipleOf", Json.createValue(new BigDecimal("1e-8"))).build() - ) - .asAssertion() - .isValidFor(Json.createValue(12391239123L)), + new MultipleOfKeyword(new BigDecimal("1e-8")).asAssertion().isValidFor(Json.createValue(12391239123L)), is(true) ); } @@ -96,16 +80,8 @@ void should_be_valid_for_any_int_if_multipleOf_is_1en8() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("multipleOf", Json.createValue(2)).build()).printOn( - new HashMapMedia() - ), + new MultipleOfKeyword(BigDecimal.valueOf(2)).printOn(new HashMapMedia()), (Matcher) hasEntry(is("multipleOf"), is(new BigDecimal(2))) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new NumberKeywordType(JsonProvider.provider(), "multipleOf", MultipleOfKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java index 817ff41a..96250f0e 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/PatternKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -42,9 +38,7 @@ class PatternKeywordTest { @Test void should_be_know_his_name() { - final Keyword pattern = createKeywordFrom( - Json.createObjectBuilder().add("pattern", Json.createValue("a")).build() - ); + final Keyword pattern = new PatternKeyword("a"); assertThat(pattern.hasName("pattern"), is(true)); assertThat(pattern.hasName("test"), is(false)); @@ -52,22 +46,13 @@ void should_be_know_his_name() { @Test void should_be_valid_for_non_string_value() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("pattern", Json.createValue("a")).build()) - .asAssertion() - .isValidFor(JsonValue.TRUE), - is(true) - ); + assertThat(new PatternKeyword("a").asAssertion().isValidFor(JsonValue.TRUE), is(true)); } @Test void should_be_invalid_for_non_matching_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ) + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") .asAssertion() .isValidFor(Json.createValue("(888)555-1212 ext. 532")), is(false) @@ -77,11 +62,7 @@ void should_be_invalid_for_non_matching_value() { @Test void should_be_valid_matching_value() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ) + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$") .asAssertion() .isValidFor(Json.createValue("(888)555-1212")), is(true) @@ -91,18 +72,8 @@ void should_be_valid_matching_value() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder() - .add("pattern", Json.createValue("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) - .build() - ).printOn(new HashMapMedia()), + new PatternKeyword("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$").printOn(new HashMapMedia()), (Matcher) hasEntry(is("pattern"), is("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringKeywordType(JsonProvider.provider(), "pattern", PatternKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java index f832e374..35374374 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/RequiredKeywordTest.java @@ -29,14 +29,11 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.StringArrayKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; +import java.util.List; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -44,9 +41,7 @@ class RequiredKeywordTest { @Test void should_know_his_name() { - final Keyword required = createKeywordFrom( - Json.createObjectBuilder().add("required", JsonValue.EMPTY_JSON_ARRAY).build() - ); + final Keyword required = new RequiredKeyword(List.of()); assertThat(required.hasName("required"), is(true)); assertThat(required.hasName("test"), is(false)); @@ -55,9 +50,7 @@ void should_know_his_name() { @Test void should_invalid_if_not_all_properties_in_the_instance() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) + new RequiredKeyword(List.of("foo", "bar")) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", BigDecimal.ONE).build()), is(false) @@ -67,11 +60,7 @@ void should_invalid_if_not_all_properties_in_the_instance() { @Test void should_valid_for_non_objects() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_ARRAY), + new RequiredKeyword(List.of("foo", "bar")).asAssertion().isValidFor(JsonValue.EMPTY_JSON_ARRAY), is(true) ); } @@ -79,9 +68,7 @@ void should_valid_for_non_objects() { @Test void should_valid_if_all_properties_are_in_the_instance() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ) + new RequiredKeyword(List.of("foo", "bar")) .asAssertion() .isValidFor(Json.createObjectBuilder().add("foo", BigDecimal.ONE).add("bar", "test").build()), is(true) @@ -91,16 +78,8 @@ void should_valid_if_all_properties_are_in_the_instance() { @Test void should_be_printable() { assertThat( - createKeywordFrom( - Json.createObjectBuilder().add("required", Json.createArrayBuilder().add("foo").add("bar")).build() - ).printOn(new HashMapMedia()), + new RequiredKeyword(List.of("foo", "bar")).printOn(new HashMapMedia()), (Matcher) hasEntry(is("required"), containsInAnyOrder("foo", "bar")) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new StringArrayKeywordType(JsonProvider.provider(), "required", RequiredKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java index 86db461c..9904d662 100644 --- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java +++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/validation/UniqueItemsKeywordTest.java @@ -28,13 +28,9 @@ import static org.hamcrest.Matchers.is; import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia; -import io.github.sebastiantoepfer.jsonschema.core.DefaultJsonSchemaFactory; -import io.github.sebastiantoepfer.jsonschema.core.keyword.type.BooleanKeywordType; import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import jakarta.json.Json; -import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import jakarta.json.spi.JsonProvider; import java.io.StringReader; import java.math.BigDecimal; import org.hamcrest.Matcher; @@ -44,9 +40,7 @@ class UniqueItemsKeywordTest { @Test void should_know_his_name() { - final Keyword keyword = createKeywordFrom( - Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build() - ); + final Keyword keyword = new UniqueItemsKeyword(false); assertThat(keyword.hasName("uniqueItems"), is(true)); assertThat(keyword.hasName("test"), is(false)); @@ -55,9 +49,7 @@ void should_know_his_name() { @Test void should_be_valid_for_uniqueItems() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("2").build()), + new UniqueItemsKeyword(true).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("2").build()), is(true) ); } @@ -65,9 +57,7 @@ void should_be_valid_for_uniqueItems() { @Test void should_be_valid_for_non_uniqueItems_if_false() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("1").build()), + new UniqueItemsKeyword(false).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("1").build()), is(true) ); } @@ -75,27 +65,20 @@ void should_be_valid_for_non_uniqueItems_if_false() { @Test void should_be_invalid_for_non_uniqueItems() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) - .asAssertion() - .isValidFor(Json.createArrayBuilder().add("1").add("1").build()), + new UniqueItemsKeyword(true).asAssertion().isValidFor(Json.createArrayBuilder().add("1").add("1").build()), is(false) ); } @Test void should_be_valid_for_non_arrays() { - assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.FALSE).build()) - .asAssertion() - .isValidFor(JsonValue.EMPTY_JSON_OBJECT), - is(true) - ); + assertThat(new UniqueItemsKeyword(false).asAssertion().isValidFor(JsonValue.EMPTY_JSON_OBJECT), is(true)); } @Test void should_be_invalid_if_numbers_mathematically_unequal() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()) + new UniqueItemsKeyword(true) .asAssertion() .isValidFor(Json.createReader(new StringReader("[1.0,1.00,1]")).readArray()), is(false) @@ -126,16 +109,8 @@ void pitests_say_i_must_write_this_tests() { @Test void should_be_printable() { assertThat( - createKeywordFrom(Json.createObjectBuilder().add("uniqueItems", JsonValue.TRUE).build()).printOn( - new HashMapMedia() - ), + new UniqueItemsKeyword(true).printOn(new HashMapMedia()), (Matcher) hasEntry(is("uniqueItems"), is(true)) ); } - - private static Keyword createKeywordFrom(final JsonObject json) { - return new BooleanKeywordType(JsonProvider.provider(), "uniqueItems", UniqueItemsKeyword::new).createKeyword( - new DefaultJsonSchemaFactory().create(json) - ); - } } diff --git a/pom.xml b/pom.xml index ad3cb22e..aa0ec438 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.1 + 0.3.0-SNAPSHOT Json Schema pom diff --git a/vocabulary-spi/pom.xml b/vocabulary-spi/pom.xml index 9074e0eb..e36b01b3 100644 --- a/vocabulary-spi/pom.xml +++ b/vocabulary-spi/pom.xml @@ -8,7 +8,7 @@ io.github.sebastian-toepfer.json-schema json-schema - 0.2.1 + 0.3.0-SNAPSHOT json-schema-vocabulary-spi @@ -42,6 +42,11 @@ hamcrest-optional test
+ + nl.jqno.equalsverifier + equalsverifier + test + jakarta.json diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java new file mode 100644 index 00000000..de1a4e2a --- /dev/null +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinition.java @@ -0,0 +1,102 @@ +/* + * 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.vocabulary.spi; + +import io.github.sebastiantoepfer.jsonschema.Vocabulary; +import java.net.URI; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class LazyVocabularyDefinition implements VocabularyDefinition { + + private final URI id; + private final boolean required; + private final Supplier> lazyVocabularies; + + public LazyVocabularyDefinition( + final URI id, + final boolean required, + final Supplier> lazyVocabularies + ) { + this.id = Objects.requireNonNull(id); + this.required = required; + this.lazyVocabularies = Objects.requireNonNull(lazyVocabularies); + } + + @Override + public Optional findVocabulary() { + final Optional result = lazyVocabularies + .get() + .map(loader -> loader.loadVocabularyWithId(id)) + .flatMap(Optional::stream) + .findFirst(); + if (result.isEmpty() && isRequired()) { + throw new IllegalStateException("can not find required vocabulary: " + id); + } + return result; + } + + @Override + public boolean hasid(final URI id) { + return Objects.equals(this.id, id); + } + + @Override + public boolean isRequired() { + return required; + } + + @Override + public String toString() { + return "LazyVocabularyDefinition{" + "id=" + id + ", required=" + required + '}'; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 59 * hash + Objects.hashCode(this.id); + hash = 59 * hash + (this.required ? 1 : 0); + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LazyVocabularyDefinition other = (LazyVocabularyDefinition) obj; + if (this.required != other.required) { + return false; + } + return Objects.equals(this.id, other.id); + } +} diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java similarity index 89% rename from vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java rename to vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java index 2d551ff6..4228a577 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabulary.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabulary.java @@ -32,16 +32,16 @@ import java.util.Objects; import java.util.Optional; -public final class DefaultVocabulary implements Vocabulary { +public final class ListVocabulary implements Vocabulary { private final URI id; private final List keywords; - public DefaultVocabulary(final URI id, final KeywordType... keywortds) { + public ListVocabulary(final URI id, final KeywordType... keywortds) { this(id, Arrays.asList(keywortds)); } - public DefaultVocabulary(final URI id, final Collection keywords) { + public ListVocabulary(final URI id, final Collection keywords) { this.id = Objects.requireNonNull(id); this.keywords = List.copyOf(keywords); } diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java new file mode 100644 index 00000000..93774c89 --- /dev/null +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ServiceLoaderLazyVocabulariesSupplier.java @@ -0,0 +1,36 @@ +/* + * 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.vocabulary.spi; + +import java.util.ServiceLoader; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public final class ServiceLoaderLazyVocabulariesSupplier implements Supplier> { + + @Override + public Stream get() { + return ServiceLoader.load(LazyVocabularies.class).stream().map(ServiceLoader.Provider::get); + } +} diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java index a14c7d61..d53c3574 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinition.java @@ -26,19 +26,11 @@ import io.github.sebastiantoepfer.jsonschema.Vocabulary; import java.net.URI; import java.util.Optional; -import java.util.ServiceLoader; -public record VocabularyDefinition(URI id, boolean required) { - public Optional findVocabulary() { - final Optional result = ServiceLoader.load(LazyVocabularies.class) - .stream() - .map(ServiceLoader.Provider::get) - .map(loader -> loader.loadVocabularyWithId(id)) - .flatMap(Optional::stream) - .findFirst(); - if (result.isEmpty() && required) { - throw new IllegalStateException("can not find required vocabulary: " + id); - } - return result; - } +public interface VocabularyDefinition { + Optional findVocabulary(); + + boolean hasid(URI id); + + boolean isRequired(); } diff --git a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java index fe9dd85f..d9822afd 100644 --- a/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java +++ b/vocabulary-spi/src/main/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitions.java @@ -23,8 +23,9 @@ */ package io.github.sebastiantoepfer.jsonschema.vocabulary.spi; +import io.github.sebastiantoepfer.jsonschema.keyword.Keyword; import java.util.stream.Stream; -public interface VocabularyDefinitions { +public interface VocabularyDefinitions extends Keyword { Stream definitions(); } diff --git a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java similarity index 60% rename from vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java rename to vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java index dd582c88..72d6ab4d 100644 --- a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/VocabularyDefinitionTest.java +++ b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/LazyVocabularyDefinitionTest.java @@ -26,24 +26,41 @@ import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty; import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; import io.github.sebastiantoepfer.jsonschema.Vocabulary; import java.net.URI; +import java.util.stream.Stream; +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; import org.junit.jupiter.api.Test; -class VocabularyDefinitionTest { +class LazyVocabularyDefinitionTest { + + @Test + void verifyEqualsContract() { + EqualsVerifier.forClass(LazyVocabularyDefinition.class).suppress(Warning.ALL_FIELDS_SHOULD_BE_USED).verify(); + } @Test void should_throw_illegal_state_if_a_required_vocabulary_can_not_be_loaded() { - final VocabularyDefinition vocabDef = new VocabularyDefinition(URI.create("https://invalid"), true); + final VocabularyDefinition vocabDef = new LazyVocabularyDefinition( + URI.create("https://invalid"), + true, + () -> Stream.empty() + ); assertThrows(IllegalStateException.class, () -> vocabDef.findVocabulary()); } @Test void should_find_mandatory_core_vocabulary() { assertThat( - new VocabularyDefinition(URI.create("https://json-schema.org/draft/2020-12/vocab/core"), true) + new LazyVocabularyDefinition( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + true, + new ServiceLoaderLazyVocabulariesSupplier() + ) .findVocabulary() .map(Vocabulary::id), isPresentAndIs(URI.create("https://json-schema.org/draft/2020-12/vocab/core")) @@ -53,8 +70,22 @@ void should_find_mandatory_core_vocabulary() { @Test void should_retrun_empty_for_optional_vocabulary_which_can_not_be_loaded() { assertThat( - new VocabularyDefinition(URI.create("https://invalid"), false).findVocabulary().map(Vocabulary::id), + new LazyVocabularyDefinition(URI.create("https://invalid"), false, () -> Stream.empty()) + .findVocabulary() + .map(Vocabulary::id), isEmpty() ); } + + @Test + void should_know_this_id() { + final LazyVocabularyDefinition vocbDef = new LazyVocabularyDefinition( + URI.create("https://json-schema.org/draft/2020-12/vocab/core"), + false, + () -> Stream.empty() + ); + + assertThat(vocbDef.hasid(URI.create("https://json-schema.org/draft/2020-12/vocab/core")), is(true)); + assertThat(vocbDef.hasid(URI.create("https://invalid")), is(false)); + } } diff --git a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java similarity index 86% rename from vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java rename to vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java index bcc681e4..96f08877 100644 --- a/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/DefaultVocabularyTest.java +++ b/vocabulary-spi/src/test/java/io/github/sebastiantoepfer/jsonschema/vocabulary/spi/ListVocabularyTest.java @@ -34,12 +34,12 @@ import java.net.URI; import org.junit.jupiter.api.Test; -class DefaultVocabularyTest { +class ListVocabularyTest { @Test void should_return_the_id() { assertThat( - new DefaultVocabulary(URI.create("http::/localhots/vocab")).id(), + new ListVocabulary(URI.create("http::/localhots/vocab")).id(), is(URI.create("http::/localhots/vocab")) ); } @@ -47,10 +47,9 @@ void should_return_the_id() { @Test void should_return_empty_if_requested_keyword_is_unknown() { assertThat( - new DefaultVocabulary( - URI.create("http://localhost"), - new SimpleTestKeywordType("name") - ).findKeywordTypeByName("test"), + new ListVocabulary(URI.create("http://localhost"), new SimpleTestKeywordType("name")).findKeywordTypeByName( + "test" + ), isEmpty() ); } @@ -59,7 +58,7 @@ void should_return_empty_if_requested_keyword_is_unknown() { void should_return_requested_keyword_if_is_it_known() { final KeywordType testKeywordType = new SimpleTestKeywordType("test"); assertThat( - new DefaultVocabulary(URI.create("http://localhost"), testKeywordType).findKeywordTypeByName("test"), + new ListVocabulary(URI.create("http://localhost"), testKeywordType).findKeywordTypeByName("test"), isPresentAndIs(testKeywordType) ); }