Skip to content

Commit

Permalink
Merge pull request #124 from sebastian-toepfer/keyword_minContains
Browse files Browse the repository at this point in the history
add support for minContains keyword
  • Loading branch information
sebastian-toepfer authored May 30, 2024
2 parents 17f657c + f73f0f6 commit 45c34e2
Show file tree
Hide file tree
Showing 13 changed files with 746 additions and 136 deletions.
3 changes: 1 addition & 2 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,12 @@
<!-- more than items keyword needed :(
<include>**/tests/draft2020-12/items.json</include>
-->
<!-- needs minContains too :(
<include>**/tests/draft2020-12/maxContains.json</include>
-->
<include>**/tests/draft2020-12/maxItems.json</include>
<include>**/tests/draft2020-12/maxLength.json</include>
<include>**/tests/draft2020-12/maximum.json</include>
<include>**/tests/draft2020-12/maxProperties.json</include>
<include>**/tests/draft2020-12/minContains.json</include>
<include>**/tests/draft2020-12/minItems.json</include>
<include>**/tests/draft2020-12/minLength.json</include>
<include>**/tests/draft2020-12/minimum.json</include>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.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.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;

public class AffectedByKeywordType implements KeywordType {

private final String name;
private final List<String> affectedBy;
private final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator;

public AffectedByKeywordType(
final String name,
final List<String> affectedBy,
final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator
) {
this.name = Objects.requireNonNull(name);
this.affectedBy = List.copyOf(affectedBy);
this.keywordCreator = Objects.requireNonNull(keywordCreator);
}

@Override
public String name() {
return name;
}

@Override
public Keyword createKeyword(final JsonSchema schema) {
return new AffectedByKeyword(schema, name, affectedBy, keywordCreator);
}

static final class AffectedByKeyword extends KeywordRelationship {

private final JsonSchema schema;
private final List<String> affectedBy;
private final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator;

public AffectedByKeyword(
final JsonSchema schema,
final String name,
final List<String> affectedBy,
final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator
) {
super(name);
this.schema = Objects.requireNonNull(schema);
this.affectedBy = List.copyOf(affectedBy);
this.keywordCreator = Objects.requireNonNull(keywordCreator);
}

@Override
protected Keyword delegate() {
return affectedBy
.stream()
.map(schema::keywordByName)
.flatMap(Optional::stream)
.collect(collectingAndThen(toList(), k -> keywordCreator.apply(k, schema)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.KeywordType;
import io.github.sebastiantoepfer.jsonschema.keyword.StaticAnnotation;
import jakarta.json.JsonValue;
import java.util.Objects;
import java.util.function.BiFunction;

public class AffectsKeywordType implements KeywordType {

private final String name;
private final String affects;
private final BiFunction<Annotation, JsonSchema, Keyword> keywordCreator;

public AffectsKeywordType(
final String name,
final String affects,
final BiFunction<Annotation, JsonSchema, Keyword> keywordCreator
) {
this.name = name;
this.affects = affects;
this.keywordCreator = keywordCreator;
}

@Override
public String name() {
return name;
}

@Override
public Keyword createKeyword(final JsonSchema schema) {
return new AffectsKeyword(schema, name, affects, keywordCreator);
}

static final class AffectsKeyword extends KeywordRelationship {

private final JsonSchema schema;
private final String affects;
private final BiFunction<Annotation, JsonSchema, Keyword> keywordCreator;

public AffectsKeyword(
final JsonSchema schema,
final String name,
final String affects,
final BiFunction<Annotation, JsonSchema, Keyword> keywordCreator
) {
super(name);
this.schema = Objects.requireNonNull(schema);
this.affects = 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
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.Keyword;
import io.github.sebastiantoepfer.jsonschema.keyword.ReservedLocation;
import java.util.Collection;
import java.util.Objects;

abstract class KeywordRelationship implements Keyword {

private final String name;

protected KeywordRelationship(final String name) {
this.name = Objects.requireNonNull(name);
}

@Override
public final Collection<Keyword.KeywordCategory> categories() {
return delegate().categories();
}

@Override
public final boolean hasName(final String name) {
return Objects.equals(this.name, name);
}

@Override
public final <T extends Media<T>> T printOn(final T media) {
return delegate().printOn(media);
}

@Override
public final Identifier asIdentifier() {
throw new UnsupportedOperationException();
}

@Override
public final Assertion asAssertion() {
return delegate().asAssertion();
}

@Override
public final Annotation asAnnotation() {
return delegate().asAnnotation();
}

@Override
public final Applicator asApplicator() {
return delegate().asApplicator();
}

@Override
public final ReservedLocation asReservedLocation() {
throw new UnsupportedOperationException();
}

protected abstract Keyword delegate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
package io.github.sebastiantoepfer.jsonschema.core.vocab.applicator;

import io.github.sebastiantoepfer.jsonschema.Vocabulary;
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.NamedJsonSchemaKeywordType;
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 java.net.URI;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -51,7 +53,13 @@ public ApplicatorVocabulary() {
new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new),
new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new),
new ArraySubSchemaKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new),
new SubSchemaKeywordType(ContainsKeyword.NAME, ContainsKeyword::new)
//normally affeced by minContains and maxContains, but only min has a direct effect!
new AffectedByKeywordType(
ContainsKeyword.NAME,
List.of("minContains"),
(a, schema) ->
new SubSchemaKeywordType(ContainsKeyword.NAME, s -> new ContainsKeyword(a, s)).createKeyword(schema)
)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,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.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;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
* <b>contains</b> : <i>Schema</i>
Expand All @@ -51,9 +53,11 @@
final class ContainsKeyword implements Applicator, Annotation {

static final String NAME = "contains";
private final JsonSubSchema contains;
private final JsonSchema contains;
private final List<Keyword> affectedBy;

public ContainsKeyword(final JsonSubSchema contains) {
public ContainsKeyword(final List<Keyword> affectedBy, final JsonSchema contains) {
this.affectedBy = List.copyOf(affectedBy);
this.contains = Objects.requireNonNull(contains);
}

Expand All @@ -78,7 +82,7 @@ public boolean applyTo(final JsonValue instance) {
}

private boolean contains(final JsonArray array) {
return array.stream().anyMatch(contains.validator()::isValid);
return !affectedBy.isEmpty() || matchingValues(array).findAny().isPresent();
}

@Override
Expand All @@ -94,12 +98,16 @@ public JsonValue valueFor(final JsonValue value) {

private JsonValue valueFor(final JsonArray values) {
final JsonValue result;
final JsonArray matchingItems = values.stream().filter(contains.validator()::isValid).collect(toJsonArray());
if (matchingItems.size() == values.size()) {
final JsonArray matchingItems = matchingValues(values).collect(toJsonArray());
if (matchingItems.size() == values.size() && !values.isEmpty()) {
result = JsonValue.TRUE;
} else {
result = matchingItems;
}
return result;
}

Stream<JsonValue> matchingValues(final JsonArray values) {
return values.stream().filter(contains.validator()::isValid);
}
}
Loading

0 comments on commit 45c34e2

Please sign in to comment.