Skip to content

Commit

Permalink
add support for if-then-else keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-toepfer committed Jun 15, 2024
1 parent 86c6dc1 commit baadf17
Show file tree
Hide file tree
Showing 15 changed files with 917 additions and 2 deletions.
1 change: 1 addition & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
<!-- how to validate?
<include>**/tests/draft2020-12/id.json</include>
-->
<include>**/tests/draft2020-12/if-then-else.json</include>
<include>**/tests/draft2020-12/items.json</include>
<include>**/tests/draft2020-12/maxContains.json</include>
<include>**/tests/draft2020-12/maxItems.json</include>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<JsonValue> connect(final Predicate<JsonValue> first, final Predicate<JsonValue> second) {
return first.negate().or(second);
}
},
INVALID {
@Override
Predicate<JsonValue> connect(final Predicate<JsonValue> first, final Predicate<JsonValue> second) {
return first.or(second);
}
};

abstract Predicate<JsonValue> connect(Predicate<JsonValue> first, Predicate<JsonValue> second);
}
Original file line number Diff line number Diff line change
@@ -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<KeywordCategory> categories() {
final Set<KeywordCategory> 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 extends Media<T>> T printOn(final T media) {
return secondChanLink.printOn(media);
}
}
Original file line number Diff line number Diff line change
@@ -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<JsonSubSchema, Keyword> keywordCreator;
private final LINKTYPE linkType;

public LinkedWith(
final String name,
final Function<JsonSubSchema, Keyword> keywordCreator,
final LINKTYPE linkType
) {
this.name = Objects.requireNonNull(name);
this.keywordCreator = Objects.requireNonNull(keywordCreator);
this.linkType = Objects.requireNonNull(linkType);
}

@Override
public Function<Keyword, Keyword> findAffectedByKeywordIn(final JsonSchema schema) {
return schema
.subSchema(name)
.map(keywordCreator)
.map(k -> (Function<Keyword, Keyword>) new KeywordLink(k, linkType))
.orElse(ReplacingKeyword::new);
}

@Override
public String toString() {
return "LinkedWith{" + "name=" + name + '}';
}

private static class KeywordLink implements Function<Keyword, Keyword> {

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<KeywordCategory> categoriesToReplace;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 :(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
* <b>else</b> : <i>Schema</i><br/>
* 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.<br/>
* <b>kind</b>
* <ul>
* <li>Applicator</li>
* </ul>
*
* 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 extends Media<T>> T printOn(final T media) {
return media.withValue(NAME, schema);
}

@Override
public boolean applyTo(final JsonValue instance) {
return schema.validator().isValid(instance);
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
* <b>if</b> : <i>Schema</i><br/>
* This keyword declares a condition based on the validation result of the given schema.<br/>
* <br/>
* <b>kind</b>
* <ul>
* <li>Applicator</li>
* </ul>
*
* 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 extends Media<T>> T printOn(final T media) {
return media.withValue(NAME, schema);
}
}
Loading

0 comments on commit baadf17

Please sign in to comment.