diff --git a/core/pom.xml b/core/pom.xml
index bc1ccaf..cf471b0 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -136,6 +136,7 @@
+ **/tests/draft2020-12/if-then-else.json
**/tests/draft2020-12/items.json
**/tests/draft2020-12/maxContains.json
**/tests/draft2020-12/maxItems.json
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java
new file mode 100644
index 0000000..9b973cb
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LINKTYPE.java
@@ -0,0 +1,44 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.keyword.type;
+
+import jakarta.json.JsonValue;
+import java.util.function.Predicate;
+
+public enum LINKTYPE {
+ VALID {
+ @Override
+ Predicate connect(final Predicate first, final Predicate second) {
+ return first.negate().or(second);
+ }
+ },
+ INVALID {
+ @Override
+ Predicate connect(final Predicate first, final Predicate second) {
+ return first.or(second);
+ }
+ };
+
+ abstract Predicate connect(Predicate first, Predicate second);
+}
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java
new file mode 100644
index 0000000..102d13b
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeyword.java
@@ -0,0 +1,70 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.keyword.type;
+
+import io.github.sebastiantoepfer.ddd.common.Media;
+import io.github.sebastiantoepfer.jsonschema.keyword.Applicator;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.JsonValue;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+
+final class LinkedKeyword implements Applicator {
+
+ private final Keyword firstChainLink;
+ private final LINKTYPE linkType;
+ private final Keyword secondChanLink;
+
+ public LinkedKeyword(final Keyword firstChainLink, final LINKTYPE linkType, final Keyword secondChanLink) {
+ this.firstChainLink = Objects.requireNonNull(firstChainLink);
+ this.linkType = Objects.requireNonNull(linkType);
+ this.secondChanLink = Objects.requireNonNull(secondChanLink);
+ }
+
+ @Override
+ public Collection categories() {
+ final Set result = EnumSet.copyOf(firstChainLink.categories());
+ result.addAll(secondChanLink.categories());
+ return result;
+ }
+
+ @Override
+ public boolean applyTo(final JsonValue instance) {
+ return linkType
+ .connect(firstChainLink.asApplicator()::applyTo, secondChanLink.asApplicator()::applyTo)
+ .test(instance);
+ }
+
+ @Override
+ public boolean hasName(final String name) {
+ return secondChanLink.hasName(name);
+ }
+
+ @Override
+ public > T printOn(final T media) {
+ return secondChanLink.printOn(media);
+ }
+}
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java
new file mode 100644
index 0000000..5b4c1ae
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWith.java
@@ -0,0 +1,77 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.keyword.type;
+
+import io.github.sebastiantoepfer.jsonschema.JsonSchema;
+import io.github.sebastiantoepfer.jsonschema.JsonSubSchema;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import java.util.Objects;
+import java.util.function.Function;
+
+public final class LinkedWith implements AffectedBy {
+
+ private final String name;
+ private final Function keywordCreator;
+ private final LINKTYPE linkType;
+
+ public LinkedWith(
+ final String name,
+ final Function keywordCreator,
+ final LINKTYPE linkType
+ ) {
+ this.name = Objects.requireNonNull(name);
+ this.keywordCreator = Objects.requireNonNull(keywordCreator);
+ this.linkType = Objects.requireNonNull(linkType);
+ }
+
+ @Override
+ public Function findAffectedByKeywordIn(final JsonSchema schema) {
+ return schema
+ .subSchema(name)
+ .map(keywordCreator)
+ .map(k -> (Function) new KeywordLink(k, linkType))
+ .orElse(ReplacingKeyword::new);
+ }
+
+ @Override
+ public String toString() {
+ return "LinkedWith{" + "name=" + name + '}';
+ }
+
+ private static class KeywordLink implements Function {
+
+ private final Keyword firstChainLink;
+ private final LINKTYPE linkType;
+
+ public KeywordLink(final Keyword firstChainLink, final LINKTYPE linkType) {
+ this.firstChainLink = Objects.requireNonNull(firstChainLink);
+ this.linkType = Objects.requireNonNull(linkType);
+ }
+
+ @Override
+ public Keyword apply(final Keyword secondChanLink) {
+ return new LinkedKeyword(firstChainLink, linkType, secondChanLink);
+ }
+ }
+}
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java
index 39d5a61..25e0a33 100644
--- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/ReplacingKeyword.java
@@ -38,7 +38,7 @@
import java.util.List;
import java.util.Objects;
-final class ReplacingKeyword implements Keyword {
+public final class ReplacingKeyword implements Keyword {
private final Keyword keywordToReplace;
private final Collection categoriesToReplace;
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java
index 15a9b37..a0c6015 100644
--- a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ApplicatorVocabulary.java
@@ -28,16 +28,21 @@
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.Affects;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.AffectsKeywordType;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ExtendedBy;
+import io.github.sebastiantoepfer.jsonschema.core.keyword.type.LINKTYPE;
+import io.github.sebastiantoepfer.jsonschema.core.keyword.type.LinkedWith;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.NamedJsonSchemaKeywordType;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ReplacedBy;
+import io.github.sebastiantoepfer.jsonschema.core.keyword.type.ReplacingKeyword;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SubSchemaKeywordType;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
import io.github.sebastiantoepfer.jsonschema.keyword.KeywordType;
import io.github.sebastiantoepfer.jsonschema.vocabulary.spi.ListVocabulary;
import jakarta.json.Json;
import jakarta.json.JsonValue;
import jakarta.json.spi.JsonProvider;
import java.net.URI;
+import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
@@ -58,6 +63,21 @@ public ApplicatorVocabulary(final JsonProvider provider) {
new SchemaArrayKeywordType(AllOfKeyword.NAME, AllOfKeyword::new),
new SchemaArrayKeywordType(AnyOfKeyword.NAME, AnyOfKeyword::new),
new SchemaArrayKeywordType(OneOfKeyword.NAME, OneOfKeyword::new),
+ new AffectedByKeywordType(
+ ThenKeyword.NAME,
+ List.of(new LinkedWith(IfKeyword.NAME, IfKeyword::new, LINKTYPE.VALID)),
+ new SubSchemaKeywordType(ThenKeyword.NAME, ThenKeyword::new)::createKeyword
+ ),
+ //if keyword as no meanings without then or else -> needs a better affects keywordtype
+ new SubSchemaKeywordType(
+ IfKeyword.NAME,
+ schema -> new ReplacingKeyword(new IfKeyword(schema), EnumSet.allOf(Keyword.KeywordCategory.class))
+ ),
+ new AffectedByKeywordType(
+ ElseKeyword.NAME,
+ List.of(new LinkedWith(IfKeyword.NAME, IfKeyword::new, LINKTYPE.INVALID)),
+ new SubSchemaKeywordType(ElseKeyword.NAME, ElseKeyword::new)::createKeyword
+ ),
new SubSchemaKeywordType(NotKeyword.NAME, NotKeyword::new),
new NamedJsonSchemaKeywordType(PropertiesKeyword.NAME, PropertiesKeyword::new),
//nomally affectedBy ... but we had the needed function only in affects :(
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java
new file mode 100644
index 0000000..5338662
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeyword.java
@@ -0,0 +1,67 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import io.github.sebastiantoepfer.ddd.common.Media;
+import io.github.sebastiantoepfer.jsonschema.JsonSchema;
+import io.github.sebastiantoepfer.jsonschema.keyword.Applicator;
+import jakarta.json.JsonValue;
+import java.util.Objects;
+
+/**
+ * else : Schema
+ * When if is present, and the instance fails to validate against its subschema, then validation succeeds against this
+ * keyword if the instance successfully validates against this keyword’s subschema.
+ * kind
+ *
+ *
+ * source: https://www.learnjsonschema.com/2020-12/applicator/else/
+ * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.3
+ */
+class ElseKeyword implements Applicator {
+
+ static final String NAME = "else";
+ private final JsonSchema schema;
+
+ public ElseKeyword(final JsonSchema schema) {
+ this.schema = Objects.requireNonNull(schema);
+ }
+
+ @Override
+ public boolean hasName(final String name) {
+ return Objects.equals(NAME, name);
+ }
+
+ @Override
+ public > T printOn(final T media) {
+ return media.withValue(NAME, schema);
+ }
+
+ @Override
+ public boolean applyTo(final JsonValue instance) {
+ return schema.validator().isValid(instance);
+ }
+}
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java
new file mode 100644
index 0000000..23fbf9b
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeyword.java
@@ -0,0 +1,67 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import io.github.sebastiantoepfer.ddd.common.Media;
+import io.github.sebastiantoepfer.jsonschema.JsonSchema;
+import io.github.sebastiantoepfer.jsonschema.keyword.Applicator;
+import jakarta.json.JsonValue;
+import java.util.Objects;
+
+/**
+ * if : Schema
+ * This keyword declares a condition based on the validation result of the given schema.
+ *
+ * kind
+ *
+ *
+ * source: https://www.learnjsonschema.com/2020-12/applicator/if/
+ * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.1
+ */
+final class IfKeyword implements Applicator {
+
+ static final String NAME = "if";
+ private final JsonSchema schema;
+
+ public IfKeyword(final JsonSchema schema) {
+ this.schema = Objects.requireNonNull(schema);
+ }
+
+ @Override
+ public boolean applyTo(final JsonValue instance) {
+ return schema.validator().isValid(instance);
+ }
+
+ @Override
+ public boolean hasName(final String name) {
+ return Objects.equals(NAME, name);
+ }
+
+ @Override
+ public > T printOn(final T media) {
+ return media.withValue(NAME, schema);
+ }
+}
diff --git a/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java
new file mode 100644
index 0000000..294e40b
--- /dev/null
+++ b/core/src/main/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeyword.java
@@ -0,0 +1,68 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import io.github.sebastiantoepfer.ddd.common.Media;
+import io.github.sebastiantoepfer.jsonschema.JsonSchema;
+import io.github.sebastiantoepfer.jsonschema.keyword.Applicator;
+import jakarta.json.JsonValue;
+import java.util.Objects;
+
+/**
+ * then : Schema
+ * When if is present, and the instance successfully validates against its subschema, then validation succeeds against
+ * this keyword if the instance also successfully validates against this keyword’s subschema.
+ *
+ * kind
+ *
+ *
+ * source: https://www.learnjsonschema.com/2020-12/applicator/then/
+ * spec: https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.2.2.2
+ */
+final class ThenKeyword implements Applicator {
+
+ static final String NAME = "then";
+ private final JsonSchema schema;
+
+ public ThenKeyword(final JsonSchema schema) {
+ this.schema = Objects.requireNonNull(schema);
+ }
+
+ @Override
+ public boolean hasName(final String name) {
+ return Objects.equals(NAME, name);
+ }
+
+ @Override
+ public > T printOn(final T media) {
+ return media.withValue(NAME, schema);
+ }
+
+ @Override
+ public boolean applyTo(final JsonValue instance) {
+ return schema.validator().isValid(instance);
+ }
+}
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java
new file mode 100644
index 0000000..f9a7db3
--- /dev/null
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedKeywordTest.java
@@ -0,0 +1,181 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.keyword.type;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+
+import io.github.sebastiantoepfer.ddd.common.Media;
+import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia;
+import io.github.sebastiantoepfer.jsonschema.keyword.Applicator;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.JsonValue;
+import java.util.Collection;
+import java.util.EnumSet;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class LinkedKeywordTest {
+
+ @Test
+ void should_return_categories_from_both_keywords() {
+ assertThat(
+ new LinkedKeyword(new MockAnnotation(), LINKTYPE.VALID, new MockApplicator()).categories(),
+ containsInAnyOrder(Keyword.KeywordCategory.ANNOTATION, Keyword.KeywordCategory.APPLICATOR)
+ );
+ }
+
+ @Nested
+ class LinkTypeIsValid {
+
+ @Test
+ void should_valid_if_both_are_valid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(true), LINKTYPE.VALID, new MockApplicator(true))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_invalid_if_first_is_valid_and_second_is_invalid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(true), LINKTYPE.VALID, new MockApplicator(false))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(false)
+ );
+ }
+
+ @Test
+ void should_valid_if_first_is_invalid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(false), LINKTYPE.VALID, new MockApplicator(true))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(true)
+ );
+ }
+ }
+
+ @Nested
+ class LinkTypeIsInvalid {
+
+ @Test
+ void should_be_valid_if_first_is_valid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(true), LINKTYPE.INVALID, new MockApplicator(false))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_be_valid_if_first_is_invalid_and_second_is_valid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(false), LINKTYPE.INVALID, new MockApplicator(true))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_be_invalid_if_both_are_invalid() {
+ assertThat(
+ new LinkedKeyword(new MockApplicator(false), LINKTYPE.INVALID, new MockApplicator(false))
+ .asApplicator()
+ .applyTo(JsonValue.TRUE),
+ is(false)
+ );
+ }
+ }
+
+ @Test
+ void should_know_his_name() {
+ final Keyword keyword = new LinkedKeyword(new MockKeyword("if"), LINKTYPE.VALID, new MockKeyword("then"));
+
+ assertThat(keyword.hasName("if"), is(false));
+ assertThat(keyword.hasName("then"), is(true));
+ }
+
+ @Test
+ void should_use_second_to_print() {
+ assertThat(
+ new LinkedKeyword(new MockKeyword("if"), LINKTYPE.VALID, new MockKeyword("then")).printOn(
+ new HashMapMedia()
+ ),
+ is(new MockKeyword("then").printOn(new HashMapMedia()))
+ );
+ }
+
+ private static final class MockAnnotation implements Keyword {
+
+ @Override
+ public Collection categories() {
+ return EnumSet.of(KeywordCategory.ANNOTATION, KeywordCategory.APPLICATOR);
+ }
+
+ @Override
+ public boolean hasName(String name) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public > T printOn(T media) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class MockApplicator implements Applicator {
+
+ private final boolean result;
+
+ public MockApplicator() {
+ this(true);
+ }
+
+ public MockApplicator(final boolean result) {
+ this.result = result;
+ }
+
+ @Override
+ public boolean applyTo(final JsonValue instance) {
+ return result;
+ }
+
+ @Override
+ public boolean hasName(String name) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public > T printOn(T media) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+}
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java
new file mode 100644
index 0000000..c0f0925
--- /dev/null
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/LinkedWithTest.java
@@ -0,0 +1,68 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.keyword.type;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.not;
+
+import io.github.sebastiantoepfer.jsonschema.JsonSchemas;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+import java.util.EnumSet;
+import org.junit.jupiter.api.Test;
+
+class LinkedWithTest {
+
+ @Test
+ void should_replace_keyword_if_previous_chain_link_not_available() {
+ assertThat(
+ new LinkedWith(
+ "if",
+ s -> new MockKeyword("if", EnumSet.of(Keyword.KeywordCategory.APPLICATOR)),
+ LINKTYPE.VALID
+ )
+ .findAffectedByKeywordIn(JsonSchemas.load(JsonValue.TRUE))
+ .apply(new MockKeyword("MOCK"))
+ .categories(),
+ not(hasItems(Keyword.KeywordCategory.ASSERTION, Keyword.KeywordCategory.APPLICATOR))
+ );
+ }
+
+ @Test
+ void should_chain_keyword_if_previous_chain_link_is_available() {
+ assertThat(
+ new LinkedWith(
+ "if",
+ s -> new MockKeyword("if", EnumSet.of(Keyword.KeywordCategory.APPLICATOR)),
+ LINKTYPE.VALID
+ )
+ .findAffectedByKeywordIn(JsonSchemas.load(Json.createObjectBuilder().add("if", JsonValue.TRUE).build()))
+ .apply(new ReplacingKeyword(new MockKeyword("MOCK"), EnumSet.of(Keyword.KeywordCategory.ASSERTION)))
+ .categories(),
+ hasItems(Keyword.KeywordCategory.APPLICATOR, Keyword.KeywordCategory.ANNOTATION)
+ );
+ }
+}
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java
index 35ba659..b1639ce 100644
--- a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/keyword/type/MockKeyword.java
@@ -38,9 +38,15 @@
final class MockKeyword implements Identifier, Assertion, Annotation, Applicator, ReservedLocation {
private final String name;
+ private final Collection categories;
public MockKeyword(final String name) {
+ this(name, EnumSet.allOf(KeywordCategory.class));
+ }
+
+ public MockKeyword(final String name, final Collection categories) {
this.name = name;
+ this.categories = EnumSet.copyOf(categories);
}
@Override
@@ -65,7 +71,7 @@ public boolean applyTo(final JsonValue instance) {
@Override
public Collection categories() {
- return EnumSet.allOf(KeywordCategory.class);
+ return EnumSet.copyOf(categories);
}
@Override
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java
new file mode 100644
index 0000000..4023f6c
--- /dev/null
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ElseKeywordTest.java
@@ -0,0 +1,82 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.is;
+
+import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia;
+import io.github.sebastiantoepfer.jsonschema.JsonSchemas;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+
+class ElseKeywordTest {
+
+ @Test
+ void should_know_his_name() {
+ final Keyword keyword = new ElseKeyword(JsonSchemas.load(JsonValue.TRUE));
+
+ assertThat(keyword.hasName("else"), is(true));
+ assertThat(keyword.hasName("test"), is(false));
+ }
+
+ @Test
+ void should_be_printable() {
+ assertThat(
+ new ElseKeyword(
+ JsonSchemas.load(
+ Json.createObjectBuilder()
+ .add(
+ "properties",
+ Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string"))
+ )
+ .add("required", Json.createArrayBuilder().add("foo"))
+ .build()
+ )
+ ).printOn(new HashMapMedia()),
+ (Matcher) hasEntry(is("else"), hasKey("properties"))
+ );
+ }
+
+ @Test
+ void should_be_valid_if_instance_successful_validates_against_schema() {
+ assertThat(
+ new ElseKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_be_invalid_if_instance_not_successful_validates_against_schema() {
+ assertThat(
+ new ElseKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(false)
+ );
+ }
+}
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java
new file mode 100644
index 0000000..f9d8500
--- /dev/null
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/IfKeywordTest.java
@@ -0,0 +1,82 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.is;
+
+import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia;
+import io.github.sebastiantoepfer.jsonschema.JsonSchemas;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+
+class IfKeywordTest {
+
+ @Test
+ void should_know_his_name() {
+ final Keyword keyword = new IfKeyword(JsonSchemas.load(JsonValue.TRUE));
+
+ assertThat(keyword.hasName("if"), is(true));
+ assertThat(keyword.hasName("test"), is(false));
+ }
+
+ @Test
+ void should_be_printable() {
+ assertThat(
+ new IfKeyword(
+ JsonSchemas.load(
+ Json.createObjectBuilder()
+ .add(
+ "properties",
+ Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string"))
+ )
+ .add("required", Json.createArrayBuilder().add("foo"))
+ .build()
+ )
+ ).printOn(new HashMapMedia()),
+ (Matcher) hasEntry(is("if"), hasKey("properties"))
+ );
+ }
+
+ @Test
+ void should_be_valid_if_instance_successful_validates_against_schema() {
+ assertThat(
+ new IfKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_be_invalid_if_instance_not_successful_validates_against_schema() {
+ assertThat(
+ new IfKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(false)
+ );
+ }
+}
diff --git a/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java
new file mode 100644
index 0000000..17d0e28
--- /dev/null
+++ b/core/src/test/java/io/github/sebastiantoepfer/jsonschema/core/vocab/applicator/ThenKeywordTest.java
@@ -0,0 +1,82 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2024 sebastian.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.is;
+
+import io.github.sebastiantoepfer.ddd.media.core.HashMapMedia;
+import io.github.sebastiantoepfer.jsonschema.JsonSchemas;
+import io.github.sebastiantoepfer.jsonschema.keyword.Keyword;
+import jakarta.json.Json;
+import jakarta.json.JsonValue;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+
+class ThenKeywordTest {
+
+ @Test
+ void should_know_his_name() {
+ final Keyword keyword = new ThenKeyword(JsonSchemas.load(JsonValue.TRUE));
+
+ assertThat(keyword.hasName("then"), is(true));
+ assertThat(keyword.hasName("test"), is(false));
+ }
+
+ @Test
+ void should_be_printable() {
+ assertThat(
+ new ThenKeyword(
+ JsonSchemas.load(
+ Json.createObjectBuilder()
+ .add(
+ "properties",
+ Json.createObjectBuilder().add("foo", Json.createObjectBuilder().add("type", "string"))
+ )
+ .add("required", Json.createArrayBuilder().add("foo"))
+ .build()
+ )
+ ).printOn(new HashMapMedia()),
+ (Matcher) hasEntry(is("then"), hasKey("properties"))
+ );
+ }
+
+ @Test
+ void should_be_valid_if_instance_successful_validates_against_schema() {
+ assertThat(
+ new ThenKeyword(JsonSchemas.load(JsonValue.TRUE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(true)
+ );
+ }
+
+ @Test
+ void should_be_invalid_if_instance_not_successful_validates_against_schema() {
+ assertThat(
+ new ThenKeyword(JsonSchemas.load(JsonValue.FALSE)).asApplicator().applyTo(JsonValue.EMPTY_JSON_OBJECT),
+ is(false)
+ );
+ }
+}