Skip to content

Commit a61cf42

Browse files
authored
Improve vocabulary support (#953)
1 parent 51fd82b commit a61cf42

File tree

8 files changed

+334
-59
lines changed

8 files changed

+334
-59
lines changed

src/main/java/com/networknt/schema/JsonMetaSchema.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,27 @@ public JsonMetaSchema build() {
168168
if (this.specification != null) {
169169
if (this.specification.getVersionFlagValue() >= SpecVersion.VersionFlag.V201909.getVersionFlagValue()) {
170170
if (!this.uri.equals(this.specification.getId())) {
171-
String validation = Vocabularies.getVocabulary(specification, "validation");
172-
if (!this.vocabularies.getOrDefault(validation, false)) {
173-
for (String keywordToRemove : Vocabularies.getKeywords("validation")) {
174-
kwords.remove(keywordToRemove);
171+
// The current design is such that the keyword map can contain things that aren't actually keywords
172+
// This means need to remove what can't be found instead of creating from scratch
173+
Map<String, Boolean> vocabularies = JsonSchemaFactory.checkVersion(this.specification)
174+
.getInstance().getVocabularies();
175+
Set<String> current = this.vocabularies.keySet();
176+
Map<String, String> format = new HashMap<>();
177+
format.put(Vocabulary.V202012_FORMAT_ANNOTATION.getId(), Vocabulary.V202012_FORMAT_ASSERTION.getId());
178+
format.put(Vocabulary.V202012_FORMAT_ASSERTION.getId(), Vocabulary.V202012_FORMAT_ANNOTATION.getId());
179+
for (String vocabularyId : vocabularies.keySet()) {
180+
if (!current.contains(vocabularyId)) {
181+
String formatVocab = format.get(vocabularyId);
182+
if (formatVocab != null) {
183+
if (current.contains(formatVocab)) {
184+
// Skip as the assertion and annotation keywords are the same
185+
continue;
186+
}
187+
}
188+
Vocabulary vocabulary = Vocabularies.getVocabulary(vocabularyId);
189+
for (String keywordToRemove : vocabulary.getKeywords()) {
190+
kwords.remove(keywordToRemove);
191+
}
175192
}
176193
}
177194
}

src/main/java/com/networknt/schema/JsonSchema.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import java.util.function.Consumer;
3131

3232
/**
33+
* Used for creating a schema with validators for validating inputs. This is
34+
* created using {@link JsonSchemaFactory#getInstance(VersionFlag, Consumer)}
35+
* and should be cached for performance.
36+
* <p>
3337
* This is the core of json constraint implementation. It parses json constraint
3438
* file and generates JsonValidators. The class is thread safe, once it is
3539
* constructed, it can be used to validate multiple json data concurrently.

src/main/java/com/networknt/schema/JsonSchemaFactory.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import com.fasterxml.jackson.databind.ObjectMapper;
2121
import com.networknt.schema.SpecVersion.VersionFlag;
22-
import com.networknt.schema.resource.*;
22+
import com.networknt.schema.resource.DefaultSchemaLoader;
23+
import com.networknt.schema.resource.SchemaLoader;
24+
import com.networknt.schema.resource.SchemaLoaders;
25+
import com.networknt.schema.resource.SchemaMapper;
26+
import com.networknt.schema.resource.SchemaMappers;
2327
import com.networknt.schema.serialization.JsonMapperFactory;
2428
import com.networknt.schema.serialization.YamlMapperFactory;
2529

@@ -30,14 +34,20 @@
3034
import java.io.InputStream;
3135
import java.net.URI;
3236
import java.util.Collection;
33-
import java.util.HashMap;
37+
import java.util.LinkedHashMap;
3438
import java.util.List;
3539
import java.util.Map;
3640
import java.util.Map.Entry;
3741
import java.util.concurrent.ConcurrentHashMap;
3842
import java.util.concurrent.ConcurrentMap;
3943
import java.util.function.Consumer;
4044

45+
/**
46+
* Factory for building {@link JsonSchema} instances.
47+
* <p>
48+
* The factory should be typically be created using
49+
* {@link #getInstance(VersionFlag, Consumer)}.
50+
*/
4151
public class JsonSchemaFactory {
4252
private static final Logger logger = LoggerFactory
4353
.getLogger(JsonSchemaFactory.class);
@@ -185,7 +195,7 @@ public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag)
185195
* be used if the input does not specify a $schema.
186196
*
187197
* @param versionFlag the default dialect
188-
* @param customizer to customze the factory
198+
* @param customizer to customize the factory
189199
* @return the factory
190200
*/
191201
public static JsonSchemaFactory getInstance(SpecVersion.VersionFlag versionFlag,
@@ -364,12 +374,11 @@ protected JsonMetaSchema loadMetaSchema(String id, SchemaValidatorsConfig config
364374
// Process vocabularies
365375
JsonNode vocabulary = schema.getSchemaNode().get("$vocabulary");
366376
if (vocabulary != null) {
367-
builder.vocabularies(new HashMap<>());
377+
builder.vocabularies(new LinkedHashMap<>());
368378
for(Entry<String, JsonNode> vocabs : vocabulary.properties()) {
369379
builder.vocabulary(vocabs.getKey(), vocabs.getValue().booleanValue());
370380
}
371381
}
372-
373382
}
374383
}
375384
return builder.build();

src/main/java/com/networknt/schema/UnionTypeValidator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public class UnionTypeValidator extends BaseJsonValidator implements JsonValidat
3434
private final List<JsonValidator> schemas = new ArrayList<JsonValidator>();
3535
private final String error;
3636

37-
3837
public UnionTypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3938
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE, validationContext);
4039
StringBuilder errorBuilder = new StringBuilder();
@@ -105,4 +104,9 @@ public void preloadJsonSchema() {
105104
validator.preloadJsonSchema();
106105
}
107106
}
107+
108+
@Override
109+
public String getKeyword() {
110+
return "type";
111+
}
108112
}

src/main/java/com/networknt/schema/Version202012.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ public JsonMetaSchema getInstance() {
5252
new NonValidationKeyword("contentSchema"),
5353
new NonValidationKeyword("examples"),
5454
new NonValidationKeyword("then"),
55-
new NonValidationKeyword("else"),
56-
new NonValidationKeyword("additionalItems")
55+
new NonValidationKeyword("else")
5756
))
5857
.vocabularies(VOCABULARY)
5958
.build();

src/main/java/com/networknt/schema/Vocabularies.java

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,69 +15,43 @@
1515
*/
1616
package com.networknt.schema;
1717

18-
import java.util.ArrayList;
1918
import java.util.HashMap;
20-
import java.util.List;
2119
import java.util.Map;
2220

2321
/**
2422
* Vocabularies.
2523
*/
2624
public class Vocabularies {
27-
private static final Map<String, List<String>> KEYWORDS_MAPPING;
25+
private static final Map<String, Vocabulary> VALUES;
2826

2927
static {
30-
Map<String, List<String>> mapping = new HashMap<>();
31-
List<String> validation = new ArrayList<>();
32-
validation.add("type");
33-
validation.add("enum");
34-
validation.add("const");
28+
Map<String, Vocabulary> mapping = new HashMap<>();
29+
mapping.put(Vocabulary.V201909_CORE.getId(), Vocabulary.V201909_CORE);
30+
mapping.put(Vocabulary.V201909_APPLICATOR.getId(), Vocabulary.V201909_APPLICATOR);
31+
mapping.put(Vocabulary.V201909_VALIDATION.getId(), Vocabulary.V201909_VALIDATION);
32+
mapping.put(Vocabulary.V201909_META_DATA.getId(), Vocabulary.V201909_META_DATA);
33+
mapping.put(Vocabulary.V201909_FORMAT.getId(), Vocabulary.V201909_FORMAT);
34+
mapping.put(Vocabulary.V201909_CONTENT.getId(), Vocabulary.V201909_CONTENT);
3535

36-
validation.add("multipleOf");
37-
validation.add("maximum");
38-
validation.add("exclusiveMaximum");
39-
validation.add("minimum");
40-
validation.add("exclusiveMinimum");
41-
42-
validation.add("maxLength");
43-
validation.add("minLength");
44-
validation.add("pattern");
36+
mapping.put(Vocabulary.V202012_CORE.getId(), Vocabulary.V202012_CORE);
37+
mapping.put(Vocabulary.V202012_APPLICATOR.getId(), Vocabulary.V202012_APPLICATOR);
38+
mapping.put(Vocabulary.V202012_UNEVALUATED.getId(), Vocabulary.V202012_UNEVALUATED);
39+
mapping.put(Vocabulary.V202012_VALIDATION.getId(), Vocabulary.V202012_VALIDATION);
40+
mapping.put(Vocabulary.V202012_META_DATA.getId(), Vocabulary.V202012_META_DATA);
41+
mapping.put(Vocabulary.V202012_FORMAT_ANNOTATION.getId(), Vocabulary.V202012_FORMAT_ANNOTATION);
42+
mapping.put(Vocabulary.V202012_FORMAT_ASSERTION.getId(), Vocabulary.V202012_FORMAT_ASSERTION);
43+
mapping.put(Vocabulary.V202012_CONTENT.getId(), Vocabulary.V202012_CONTENT);
4544

46-
validation.add("maxItems");
47-
validation.add("minItems");
48-
validation.add("uniqueItems");
49-
validation.add("maxContains");
50-
validation.add("minContains");
51-
52-
validation.add("maxProperties");
53-
validation.add("minProperties");
54-
validation.add("required");
55-
validation.add("dependentRequired");
56-
57-
mapping.put("validation", validation);
58-
59-
KEYWORDS_MAPPING = mapping;
45+
VALUES = mapping;
6046
}
6147

6248
/**
63-
* Gets the keywords associated with a vocabulary.
49+
* Gets the vocabulary given its id.
6450
*
6551
* @param vocabulary the vocabulary
66-
* @return the keywords
52+
* @return the vocabulary
6753
*/
68-
public static List<String> getKeywords(String vocabulary) {
69-
return KEYWORDS_MAPPING.get(vocabulary);
70-
}
71-
72-
/**
73-
* Gets the vocabulary IRI.
74-
*
75-
* @param specification the specification
76-
* @param vocabulary the vocabulary
77-
* @return the vocabulary IRI
78-
*/
79-
public static String getVocabulary(SpecVersion.VersionFlag specification, String vocabulary) {
80-
String base = specification.getId().substring(0, specification.getId().lastIndexOf('/'));
81-
return base + "/vocab/" + vocabulary;
54+
public static Vocabulary getVocabulary(String vocabulary) {
55+
return VALUES.get(vocabulary);
8256
}
8357
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright (c) 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.networknt.schema;
17+
18+
import java.util.LinkedHashSet;
19+
import java.util.Objects;
20+
import java.util.Set;
21+
22+
/**
23+
* Represents a vocabulary in meta schema.
24+
* <p>
25+
* This contains the id and the related keywords.
26+
*/
27+
public class Vocabulary {
28+
29+
// 2019-09
30+
public static final Vocabulary V201909_CORE = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/core",
31+
"$id", "$schema", "$anchor", "$ref", "$recursiveRef", "$recursiveAnchor", "$vocabulary", "$comment",
32+
"$defs");
33+
public static final Vocabulary V201909_APPLICATOR = new Vocabulary(
34+
"https://json-schema.org/draft/2019-09/vocab/applicator", "additionalItems", "unevaluatedItems", "items",
35+
"contains", "additionalProperties", "unevaluatedProperties", "properties", "patternProperties",
36+
"dependentSchemas", "propertyNames", "if", "then", "else", "allOf", "anyOf", "oneOf", "not");
37+
public static final Vocabulary V201909_VALIDATION = new Vocabulary(
38+
"https://json-schema.org/draft/2019-09/vocab/validation", "multipleOf", "maximum", "exclusiveMaximum",
39+
"minimum", "exclusiveMinimum", "maxLength", "minLength", "pattern", "maxItems", "minItems", "uniqueItems",
40+
"maxContains", "minContains", "maxProperties", "minProperties", "required", "dependentRequired", "const",
41+
"enum", "type");
42+
public static final Vocabulary V201909_META_DATA = new Vocabulary(
43+
"https://json-schema.org/draft/2019-09/vocab/meta-data", "title", "description", "default", "deprecated",
44+
"readOnly", "writeOnly", "examples");
45+
public static final Vocabulary V201909_FORMAT = new Vocabulary("https://json-schema.org/draft/2019-09/vocab/format",
46+
"format");
47+
public static final Vocabulary V201909_CONTENT = new Vocabulary(
48+
"https://json-schema.org/draft/2019-09/vocab/content", "contentMediaType", "contentEncoding",
49+
"contentSchema");
50+
51+
// 2020-12
52+
public static final Vocabulary V202012_CORE = new Vocabulary("https://json-schema.org/draft/2020-12/vocab/core",
53+
"$id", "$schema", "$ref", "$anchor", "$dynamicRef", "$dynamicAnchor", "$vocabulary", "$comment", "$defs");
54+
public static final Vocabulary V202012_APPLICATOR = new Vocabulary(
55+
"https://json-schema.org/draft/2020-12/vocab/applicator", "prefixItems", "items", "contains",
56+
"additionalProperties", "properties", "patternProperties", "dependentSchemas", "propertyNames", "if",
57+
"then", "else", "allOf", "anyOf", "oneOf", "not");
58+
public static final Vocabulary V202012_UNEVALUATED = new Vocabulary(
59+
"https://json-schema.org/draft/2020-12/vocab/unevaluated", "unevaluatedItems", "unevaluatedProperties");
60+
public static final Vocabulary V202012_VALIDATION = new Vocabulary(
61+
"https://json-schema.org/draft/2020-12/vocab/validation", "type", "const", "enum", "multipleOf", "maximum",
62+
"exclusiveMaximum", "minimum", "exclusiveMinimum", "maxLength", "minLength", "pattern", "maxItems",
63+
"minItems", "uniqueItems", "maxContains", "minContains", "maxProperties", "minProperties", "required",
64+
"dependentRequired");
65+
public static final Vocabulary V202012_META_DATA = new Vocabulary(
66+
"https://json-schema.org/draft/2020-12/vocab/meta-data", "title", "description", "default", "deprecated",
67+
"readOnly", "writeOnly", "examples");
68+
public static final Vocabulary V202012_FORMAT_ANNOTATION = new Vocabulary(
69+
"https://json-schema.org/draft/2020-12/vocab/format-annotation", "format");
70+
public static final Vocabulary V202012_FORMAT_ASSERTION = new Vocabulary(
71+
"https://json-schema.org/draft/2020-12/vocab/format-assertion", "format");
72+
public static final Vocabulary V202012_CONTENT = new Vocabulary(
73+
"https://json-schema.org/draft/2020-12/vocab/content", "contentEncoding", "contentMediaType",
74+
"contentSchema");
75+
76+
private final String id;
77+
private final Set<String> keywords;
78+
79+
/**
80+
* Constructor.
81+
*
82+
* @param id the id
83+
* @param keywords the keywords
84+
*/
85+
public Vocabulary(String id, String... keywords) {
86+
this.id = id;
87+
this.keywords = new LinkedHashSet<>();
88+
for (String keyword : keywords) {
89+
this.keywords.add(keyword);
90+
}
91+
}
92+
93+
/**
94+
* The id of the vocabulary.
95+
*
96+
* @return the id
97+
*/
98+
public String getId() {
99+
return id;
100+
}
101+
102+
/**
103+
* The keywords in the vocabulary.
104+
*
105+
* @return the keywords
106+
*/
107+
public Set<String> getKeywords() {
108+
return keywords;
109+
}
110+
111+
@Override
112+
public int hashCode() {
113+
return Objects.hash(id, keywords);
114+
}
115+
116+
@Override
117+
public boolean equals(Object obj) {
118+
if (this == obj)
119+
return true;
120+
if (obj == null)
121+
return false;
122+
if (getClass() != obj.getClass())
123+
return false;
124+
Vocabulary other = (Vocabulary) obj;
125+
return Objects.equals(id, other.id) && Objects.equals(keywords, other.keywords);
126+
}
127+
128+
@Override
129+
public String toString() {
130+
return "Vocabulary [id=" + id + ", keywords=" + keywords + "]";
131+
}
132+
133+
}

0 commit comments

Comments
 (0)