Skip to content

Commit 805e1fb

Browse files
authored
Merge pull request #83 from harrel56/feature/draft2019-09
Feature/draft2019-09
2 parents c351710 + 6243cc7 commit 805e1fb

File tree

259 files changed

+16760
-784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

259 files changed

+16760
-784
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
Java library implementing [JSON schema specification](https://json-schema.org/specification.html):
99
- compatible with Java 8,
10-
- support for the newest specification draft (*2020-12*) [![Supported spec](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie-json-schema.github.io%2Fbowtie%2Fbadges%2Fjava-json-schema%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-json-schema) [![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie-json-schema.github.io%2Fbowtie%2Fbadges%2Fjava-json-schema%2Fcompliance%2FDraft_2020-12.json)](https://bowtie.report/#/implementations/java-json-schema),
10+
- support for the newest specification versions [![Supported spec](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie-json-schema.github.io%2Fbowtie%2Fbadges%2Fjava-json-schema%2Fsupported_versions.json)](https://bowtie.report/#/implementations/java-json-schema):
11+
- Draft 2019-09 [![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie-json-schema.github.io%2Fbowtie%2Fbadges%2Fjava-json-schema%2Fcompliance%2FDraft_2019-09.json)](https://bowtie.report/#/implementations/java-json-schema),
12+
- Draft 2020-12 [![Compliance](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie-json-schema.github.io%2Fbowtie%2Fbadges%2Fjava-json-schema%2Fcompliance%2FDraft_2020-12.json)](https://bowtie.report/#/implementations/java-json-schema),
1113
- support for custom keywords,
1214
- support for annotation collection,
1315
- multiple JSON providers to choose from ([supported JSON libraries](#json-providers))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package dev.harrel.jsonschema;
2+
3+
import java.util.*;
4+
import java.util.function.BiFunction;
5+
6+
import static dev.harrel.jsonschema.Keyword.*;
7+
import static java.util.Collections.*;
8+
9+
abstract class AbstractEvaluatorFactory implements EvaluatorFactory {
10+
private final Set<String> ignoredKeywords;
11+
private final Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> evaluatorsMap;
12+
13+
AbstractEvaluatorFactory(List<String> ignoredKeywords) {
14+
this.ignoredKeywords = unmodifiableSet(new HashSet<>(ignoredKeywords));
15+
Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> evaluators = getDefaultEvaluatorsMap();
16+
configureEvaluatorsMap(evaluators);
17+
this.evaluatorsMap = unmodifiableMap(evaluators);
18+
}
19+
20+
abstract void configureEvaluatorsMap(Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> evaluatorsMap);
21+
22+
@Override
23+
public Optional<Evaluator> create(SchemaParsingContext ctx, String fieldName, JsonNode node) {
24+
if (ignoredKeywords.contains(fieldName)) {
25+
return Optional.empty();
26+
}
27+
if (evaluatorsMap.containsKey(fieldName)) {
28+
try {
29+
return Optional.of(evaluatorsMap.get(fieldName).apply(ctx, node));
30+
} catch (Exception e) {
31+
return Optional.empty();
32+
}
33+
}
34+
if (node.isString()) {
35+
return Optional.of(new AnnotationEvaluator(node.asString()));
36+
}
37+
return Optional.empty();
38+
}
39+
40+
private static Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> getDefaultEvaluatorsMap() {
41+
Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> map = new HashMap<>();
42+
map.put(TYPE, (ctx, node) -> new TypeEvaluator(node));
43+
map.put(CONST, (ctx, node) -> new ConstEvaluator(node));
44+
map.put(ENUM, (ctx, node) -> new EnumEvaluator(node));
45+
map.put(MULTIPLE_OF, (ctx, node) -> new MultipleOfEvaluator(node));
46+
map.put(MAXIMUM, (ctx, node) -> new MaximumEvaluator(node));
47+
map.put(EXCLUSIVE_MAXIMUM, (ctx, node) -> new ExclusiveMaximumEvaluator(node));
48+
map.put(MINIMUM, (ctx, node) -> new MinimumEvaluator(node));
49+
map.put(EXCLUSIVE_MINIMUM, (ctx, node) -> new ExclusiveMinimumEvaluator(node));
50+
map.put(MAX_LENGTH, (ctx, node) -> new MaxLengthEvaluator(node));
51+
map.put(MIN_LENGTH, (ctx, node) -> new MinLengthEvaluator(node));
52+
map.put(PATTERN, (ctx, node) -> new PatternEvaluator(node));
53+
map.put(MAX_ITEMS, (ctx, node) -> new MaxItemsEvaluator(node));
54+
map.put(MIN_ITEMS, (ctx, node) -> new MinItemsEvaluator(node));
55+
map.put(UNIQUE_ITEMS, (ctx, node) -> new UniqueItemsEvaluator(node));
56+
map.put(MAX_CONTAINS, (ctx, node) -> new MaxContainsEvaluator(node));
57+
map.put(MIN_CONTAINS, (ctx, node) -> new MinContainsEvaluator(node));
58+
map.put(MAX_PROPERTIES, (ctx, node) -> new MaxPropertiesEvaluator(node));
59+
map.put(MIN_PROPERTIES, (ctx, node) -> new MinPropertiesEvaluator(node));
60+
map.put(REQUIRED, (ctx, node) -> new RequiredEvaluator(node));
61+
map.put(DEPENDENT_REQUIRED, (ctx, node) -> new DependentRequiredEvaluator(node));
62+
63+
map.put(CONTAINS, ContainsEvaluator::new);
64+
map.put(ADDITIONAL_PROPERTIES, AdditionalPropertiesEvaluator::new);
65+
map.put(PROPERTIES, PropertiesEvaluator::new);
66+
map.put(PATTERN_PROPERTIES, PatternPropertiesEvaluator::new);
67+
map.put(DEPENDENT_SCHEMAS, DependentSchemasEvaluator::new);
68+
map.put(PROPERTY_NAMES, PropertyNamesEvaluator::new);
69+
map.put(IF, IfThenElseEvaluator::new);
70+
map.put(ALL_OF, AllOfEvaluator::new);
71+
map.put(ANY_OF, AnyOfEvaluator::new);
72+
map.put(ONE_OF, OneOfEvaluator::new);
73+
map.put(NOT, NotEvaluator::new);
74+
map.put(UNEVALUATED_ITEMS, UnevaluatedItemsEvaluator::new);
75+
map.put(UNEVALUATED_PROPERTIES, UnevaluatedPropertiesEvaluator::new);
76+
map.put(REF, (ctx, node) -> new RefEvaluator(node));
77+
return map;
78+
}
79+
80+
static class AnnotationEvaluator implements Evaluator {
81+
private final String annotation;
82+
83+
public AnnotationEvaluator(String annotation) {
84+
this.annotation = annotation;
85+
}
86+
87+
@Override
88+
public Result evaluate(EvaluationContext ctx, JsonNode node) {
89+
return Result.success(annotation);
90+
}
91+
}
92+
}

src/main/java/dev/harrel/jsonschema/Applicators.java

+112
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,93 @@ public int getOrder() {
7979
}
8080
}
8181

82+
class Items2019Evaluator implements Evaluator {
83+
private final String schemaRef;
84+
private final List<String> schemaRefs;
85+
86+
Items2019Evaluator(SchemaParsingContext ctx, JsonNode node) {
87+
if (node.isObject() || node.isBoolean()) {
88+
this.schemaRef = ctx.getAbsoluteUri(node);
89+
this.schemaRefs = null;
90+
} else if (node.isArray()) {
91+
this.schemaRef = null;
92+
this.schemaRefs = unmodifiableList(node.asArray().stream()
93+
.map(ctx::getAbsoluteUri)
94+
.collect(Collectors.toList()));
95+
} else {
96+
throw new IllegalArgumentException();
97+
}
98+
}
99+
100+
@Override
101+
public Set<String> getVocabularies() {
102+
return APPLICATOR_VOCABULARY;
103+
}
104+
105+
@Override
106+
public Result evaluate(EvaluationContext ctx, JsonNode node) {
107+
if (!node.isArray()) {
108+
return Result.success();
109+
}
110+
List<JsonNode> array = node.asArray();
111+
if (schemaRef != null) {
112+
boolean valid = array.stream()
113+
.filter(element -> ctx.resolveInternalRefAndValidate(schemaRef, element))
114+
.count() == array.size();
115+
return valid ? Result.success(true) : Result.failure();
116+
} else {
117+
int size = Math.min(schemaRefs.size(), array.size());
118+
boolean valid = IntStream.range(0, size)
119+
.boxed()
120+
.filter(idx -> ctx.resolveInternalRefAndValidate(schemaRefs.get(idx), array.get(idx)))
121+
.count() == size;
122+
return valid ? Result.success(schemaRefs.size()) : Result.failure();
123+
}
124+
}
125+
}
126+
127+
class AdditionalItemsEvaluator implements Evaluator {
128+
private static final Set<String> vocabularies = singleton(Vocabulary.Draft2019.APPLICATOR);
129+
private final String schemaRef;
130+
131+
AdditionalItemsEvaluator(SchemaParsingContext ctx, JsonNode node) {
132+
if (!node.isObject() && !node.isBoolean()) {
133+
throw new IllegalArgumentException();
134+
}
135+
this.schemaRef = ctx.getAbsoluteUri(node);
136+
}
137+
138+
@Override
139+
public Set<String> getVocabularies() {
140+
return vocabularies;
141+
}
142+
143+
@Override
144+
public Result evaluate(EvaluationContext ctx, JsonNode node) {
145+
if (!node.isArray()) {
146+
return Result.success();
147+
}
148+
List<JsonNode> array = node.asArray();
149+
Optional<Object> itemsAnnotation = ctx.getSiblingAnnotation(Keyword.ITEMS);
150+
boolean shouldSkip = itemsAnnotation.map(Boolean.class::isInstance).orElse(false);
151+
if (shouldSkip) {
152+
return Result.success(true);
153+
}
154+
int itemsSize = itemsAnnotation.filter(Integer.class::isInstance).map(Integer.class::cast).orElse(array.size());
155+
int size = Math.max(array.size() - itemsSize, 0);
156+
boolean valid = array.stream()
157+
.skip(itemsSize)
158+
.filter(element -> ctx.resolveInternalRefAndValidate(schemaRef, element))
159+
.count() == size;
160+
return valid ? Result.success(true) : Result.failure();
161+
}
162+
163+
@Override
164+
public int getOrder() {
165+
return 10;
166+
}
167+
}
168+
82169
class ContainsEvaluator implements Evaluator {
83170
private final String schemaRef;
84171
private final boolean minContainsZero;
@@ -600,6 +687,31 @@ public Result evaluate(EvaluationContext ctx, JsonNode node) {
600687
}
601688
}
602689

690+
@Override
691+
public Set<String> getVocabularies() {
692+
return Vocabulary.CORE_VOCABULARY;
693+
}
694+
}
695+
696+
class RecursiveRefEvaluator implements Evaluator {
697+
private final String ref;
698+
699+
RecursiveRefEvaluator(JsonNode node) {
700+
if (!node.isString()) {
701+
throw new IllegalArgumentException();
702+
}
703+
this.ref = node.asString();
704+
}
705+
706+
@Override
707+
public Result evaluate(EvaluationContext ctx, JsonNode node) {
708+
try {
709+
return ctx.resolveRecursiveRefAndValidate(ref, node) ? Result.success() : Result.failure();
710+
} catch (SchemaNotFoundException e) {
711+
return Result.failure(String.format("Resolution of $recursiveRef [%s] failed", ref));
712+
}
713+
}
714+
603715
@Override
604716
public Set<String> getVocabularies() {
605717
return Vocabulary.CORE_VOCABULARY;

src/main/java/dev/harrel/jsonschema/Dialects.java

+62-10
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import java.util.Map;
55
import java.util.Set;
66

7-
import static dev.harrel.jsonschema.Vocabulary.Draft2020.*;
7+
import static dev.harrel.jsonschema.Vocabulary.*;
88
import static java.util.Collections.singleton;
99
import static java.util.Collections.unmodifiableMap;
1010

1111
/**
1212
* Static container class for all officially supported dialects.
1313
*/
14-
public class Dialects {
14+
public final class Dialects {
1515
private Dialects() {}
1616

1717
/**
@@ -24,15 +24,15 @@ public static class Draft2020Dialect implements Dialect {
2424

2525
public Draft2020Dialect() {
2626
this.evaluatorFactory = new Draft2020EvaluatorFactory();
27-
this.requiredVocabularies = singleton(CORE);
27+
this.requiredVocabularies = singleton(Draft2020.CORE);
2828
Map<String, Boolean> vocabs = new HashMap<>();
29-
vocabs.put(CORE, true);
30-
vocabs.put(APPLICATOR, true);
31-
vocabs.put(UNEVALUATED, true);
32-
vocabs.put(VALIDATION, true);
33-
vocabs.put(META_DATA, true);
34-
vocabs.put(FORMAT_ANNOTATION, true);
35-
vocabs.put(CONTENT, true);
29+
vocabs.put(Draft2020.CORE, true);
30+
vocabs.put(Draft2020.APPLICATOR, true);
31+
vocabs.put(Draft2020.UNEVALUATED, true);
32+
vocabs.put(Draft2020.VALIDATION, true);
33+
vocabs.put(Draft2020.META_DATA, true);
34+
vocabs.put(Draft2020.FORMAT_ANNOTATION, true);
35+
vocabs.put(Draft2020.CONTENT, true);
3636
this.defaultVocabularyObject = unmodifiableMap(vocabs);
3737
}
3838

@@ -66,4 +66,56 @@ public Map<String, Boolean> getDefaultVocabularyObject() {
6666
return defaultVocabularyObject;
6767
}
6868
}
69+
70+
/**
71+
* Dialect corresponding to <i>draft2019-09</i> specification.
72+
*/
73+
public static class Draft2019Dialect implements Dialect {
74+
private final EvaluatorFactory evaluatorFactory;
75+
private final Set<String> requiredVocabularies;
76+
private final Map<String, Boolean> defaultVocabularyObject;
77+
78+
public Draft2019Dialect() {
79+
this.evaluatorFactory = new Draft2019EvaluatorFactory();
80+
this.requiredVocabularies = singleton(Draft2019.CORE);
81+
Map<String, Boolean> vocabs = new HashMap<>();
82+
vocabs.put(Draft2019.CORE, true);
83+
vocabs.put(Draft2019.APPLICATOR, true);
84+
vocabs.put(Draft2019.VALIDATION, true);
85+
vocabs.put(Draft2019.META_DATA, true);
86+
vocabs.put(Draft2019.FORMAT, false);
87+
vocabs.put(Draft2019.CONTENT, true);
88+
this.defaultVocabularyObject = unmodifiableMap(vocabs);
89+
}
90+
91+
@Override
92+
public SpecificationVersion getSpecificationVersion() {
93+
return SpecificationVersion.DRAFT2019_09;
94+
}
95+
96+
@Override
97+
public String getMetaSchema() {
98+
return SpecificationVersion.DRAFT2019_09.getId();
99+
}
100+
101+
@Override
102+
public EvaluatorFactory getEvaluatorFactory() {
103+
return evaluatorFactory;
104+
}
105+
106+
@Override
107+
public Set<String> getSupportedVocabularies() {
108+
return defaultVocabularyObject.keySet();
109+
}
110+
111+
@Override
112+
public Set<String> getRequiredVocabularies() {
113+
return requiredVocabularies;
114+
}
115+
116+
@Override
117+
public Map<String, Boolean> getDefaultVocabularyObject() {
118+
return defaultVocabularyObject;
119+
}
120+
}
69121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dev.harrel.jsonschema;
2+
3+
import java.util.*;
4+
import java.util.function.BiFunction;
5+
6+
import static dev.harrel.jsonschema.Keyword.*;
7+
8+
/**
9+
* {@code EvaluatorFactory} implementation that supports <a href="https://json-schema.org/draft/2019-09/schema">2019 draft</a> specification.
10+
*/
11+
public class Draft2019EvaluatorFactory extends AbstractEvaluatorFactory {
12+
public Draft2019EvaluatorFactory() {
13+
super(Arrays.asList(ID, SCHEMA, ANCHOR, RECURSIVE_ANCHOR, VOCABULARY, COMMENT, DEFS, THEN, ELSE));
14+
}
15+
16+
@Override
17+
void configureEvaluatorsMap(Map<String, BiFunction<SchemaParsingContext, JsonNode, Evaluator>> map) {
18+
map.put(ITEMS, Items2019Evaluator::new);
19+
map.put(ADDITIONAL_ITEMS, AdditionalItemsEvaluator::new);
20+
map.put(RECURSIVE_REF, (ctx, node) -> new RecursiveRefEvaluator(node));
21+
}
22+
}

0 commit comments

Comments
 (0)