Skip to content

Commit

Permalink
introduce a more flexible way to define affectedBy relationships of k…
Browse files Browse the repository at this point in the history
…eyword

instead of define a affectedBy relationship with list of strings we us
a list of new introduces object AffectedBy with a name (the old
string) and a new AffectByType. The AffectByType can be
Extends this only shows that the keyword stays not alone and the
validation result can be altered. Or Replace with how the names say
replace the affected keyword with the affectedBy keyword. this allow
to change the validation result in both directions und a more flexible
way to do it.
  • Loading branch information
sebastian-toepfer committed Jun 5, 2024
1 parent 1802403 commit a37f02b
Show file tree
Hide file tree
Showing 11 changed files with 609 additions and 63 deletions.
20 changes: 12 additions & 8 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,7 @@
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<scope>provided</scope>
</dependency>

</dependency>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>parsson</artifactId>
Expand All @@ -99,6 +92,17 @@
<artifactId>media-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.github.spotbugs</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<AffectedBy> {

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<Keyword, Keyword> findAffectedByKeywordIn(final JsonSchema schema) {
final UnaryOperator<Keyword> 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 + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,26 @@
*/
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 {

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

public AffectedByKeywordType(
final String name,
final List<String> affectedBy,
final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator
final Collection<AffectedBy> affectedBy,
final Function<JsonSchema, Keyword> keywordCreator
) {
this.name = Objects.requireNonNull(name);
this.affectedBy = List.copyOf(affectedBy);
Expand All @@ -57,34 +56,35 @@ 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<String> affectedBy;
private final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator;
private final SortedSet<AffectedBy> affectedBy;
private final Function<JsonSchema, Keyword> keywordCreator;

public AffectedByKeyword(
public AffectedKeyword(
final JsonSchema schema,
final String name,
final List<String> affectedBy,
final BiFunction<List<Keyword>, JsonSchema, Keyword> keywordCreator
final Collection<AffectedBy> affectedBy,
final Function<JsonSchema, Keyword> keywordCreator
) {
super(name);
this.schema = Objects.requireNonNull(schema);
this.affectedBy = List.copyOf(affectedBy);
this.affectedBy = new TreeSet<>(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)));
.map(a -> a.findAffectedByKeywordIn(schema))
.reduce(Function::andThen)
.orElseThrow()
.apply(keywordCreator.apply(schema));
}
}
}
Original file line number Diff line number Diff line change
@@ -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 affectedKeyword;
private final Collection<KeywordCategory> categoriesToReplace;

public ReplacingKeyword(final Keyword affectedKeyword) {
this(affectedKeyword, EnumSet.of(KeywordCategory.APPLICATOR, KeywordCategory.ASSERTION));
}

public ReplacingKeyword(final Keyword affectedKeyword, final Collection<KeywordCategory> categoriesToReplace) {
this.affectedKeyword = Objects.requireNonNull(affectedKeyword);
this.categoriesToReplace = List.copyOf(categoriesToReplace);
}

@Override
public Identifier asIdentifier() {
if (categoriesToReplace.contains(KeywordCategory.IDENTIFIER)) {
throw new UnsupportedOperationException();
} else {
return affectedKeyword.asIdentifier();
}
}

@Override
public Assertion asAssertion() {
if (categoriesToReplace.contains(KeywordCategory.ASSERTION)) {
throw new UnsupportedOperationException();
} else {
return affectedKeyword.asAssertion();
}
}

@Override
public Annotation asAnnotation() {
if (categoriesToReplace.contains(KeywordCategory.ANNOTATION)) {
throw new UnsupportedOperationException();
} else {
return affectedKeyword.asAnnotation();
}
}

@Override
public Applicator asApplicator() {
if (categoriesToReplace.contains(KeywordCategory.APPLICATOR)) {
throw new UnsupportedOperationException();
} else {
return affectedKeyword.asApplicator();
}
}

@Override
public ReservedLocation asReservedLocation() {
if (categoriesToReplace.contains(KeywordCategory.RESERVED_LOCATION)) {
throw new UnsupportedOperationException();
} else {
return affectedKeyword.asReservedLocation();
}
}

@Override
public Collection<Keyword.KeywordCategory> categories() {
return affectedKeyword.categories().stream().filter(not(categoriesToReplace::contains)).collect(toSet());
}

@Override
public boolean hasName(final String string) {
return affectedKeyword.hasName(string);
}

@Override
public <T extends Media<T>> T printOn(final T media) {
return affectedKeyword.printOn(media);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
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.NamedJsonSchemaKeywordType;
import io.github.sebastiantoepfer.jsonschema.core.keyword.type.SchemaArrayKeywordType;
Expand Down Expand Up @@ -56,12 +58,14 @@ public ApplicatorVocabulary() {
new NamedJsonSchemaKeywordType(PatternPropertiesKeyword.NAME, PatternPropertiesKeyword::new),
new SubSchemaKeywordType(ItemsKeyword.NAME, ItemsKeyword::new),
new SchemaArrayKeywordType(PrefixItemsKeyword.NAME, PrefixItemsKeyword::new),
//normally affeced by minContains and maxContains, but only min has a direct effect!
new ArraySubSchemaKeywordType(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
)
);
}
Expand Down
Loading

0 comments on commit a37f02b

Please sign in to comment.