Skip to content

Commit f7d767a

Browse files
author
Jake Waffle
committed
Updated the RefValidator so that it is more robust and supports relative references.
1 parent d2a3a8d commit f7d767a

20 files changed

+292
-113
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, Jso
4747
additionalPropertiesSchema = null;
4848
} else if (schemaNode.isObject()) {
4949
allowAdditionalProperties = true;
50-
additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
50+
additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode, parentSchema);
5151
} else {
5252
allowAdditionalProperties = false;
5353
additionalPropertiesSchema = null;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
3535
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext);
3636
int size = schemaNode.size();
3737
for (int i = 0; i < size; i++) {
38-
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
38+
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode.get(i), parentSchema));
3939
}
4040
}
4141

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
3636
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext);
3737
int size = schemaNode.size();
3838
for (int i = 0; i < size; i++) {
39-
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
39+
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode.get(i), parentSchema));
4040
}
4141
}
4242

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema
4444
depsProps.add(pvalue.get(i).asText());
4545
}
4646
} else if (pvalue.isObject()) {
47-
schemaDeps.put(pname, new JsonSchema(validationContext, pname, pvalue, parentSchema));
47+
schemaDeps.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUrl(), pvalue, parentSchema));
4848
}
4949
}
5050

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
3838
public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3939
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
4040
if (schemaNode.isObject()) {
41-
schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
41+
schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode, parentSchema);
4242
} else {
4343
tupleSchema = new ArrayList<JsonSchema>();
4444
for (JsonNode s : schemaNode) {
45-
tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), s, parentSchema));
45+
tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), s, parentSchema));
4646
}
4747

4848
JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS);
4949
if (addItemNode != null) {
5050
if (addItemNode.isBoolean()) {
5151
additionalItems = addItemNode.asBoolean();
5252
} else if (addItemNode.isObject()) {
53-
additionalSchema = new JsonSchema(validationContext, addItemNode);
53+
additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUrl(), addItemNode);
5454
}
5555
}
5656
}

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

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@
1616

1717
package com.networknt.schema;
1818

19-
import com.fasterxml.jackson.databind.JsonNode;
20-
2119
import java.io.UnsupportedEncodingException;
20+
import java.net.MalformedURLException;
21+
import java.net.URL;
2222
import java.net.URLDecoder;
23-
import java.util.*;
23+
import java.util.Collections;
24+
import java.util.HashMap;
25+
import java.util.Iterator;
26+
import java.util.LinkedHashSet;
27+
import java.util.Map;
28+
import java.util.Set;
2429
import java.util.regex.Matcher;
2530
import java.util.regex.Pattern;
2631

32+
import com.fasterxml.jackson.databind.JsonNode;
33+
import com.networknt.schema.url.URLFactory;
34+
2735
/**
2836
* This is the core of json constraint implementation. It parses json constraint
2937
* file and generates JsonValidators. The class is thread safe, once it is
@@ -34,27 +42,60 @@ public class JsonSchema extends BaseJsonValidator {
3442
protected final Map<String, JsonValidator> validators;
3543
private final ValidationContext validationContext;
3644

45+
/**
46+
* This is the current url of this schema. This url could refer to the url of this schema's file
47+
* or it could potentially be a url that has been altered by an id. An 'id' is able to completely overwrite
48+
* the current url or add onto it. This is necessary so that '$ref's are able to be relative to a
49+
* combination of the current schema file's url and 'id' urls visible to this schema.
50+
*
51+
* This can be null. If it is null, then the creation of relative urls will fail. However, an absolute
52+
* 'id' would still be able to specify an absolute url.
53+
*/
54+
private final URL currentUrl;
55+
3756
private JsonValidator requiredValidator = null;
3857

39-
public JsonSchema(ValidationContext validationContext, JsonNode schemaNode) {
40-
this(validationContext, "#", schemaNode, null);
58+
public JsonSchema(ValidationContext validationContext, URL baseUrl, JsonNode schemaNode) {
59+
this(validationContext, "#", baseUrl, schemaNode, null);
4160
}
4261

43-
public JsonSchema(ValidationContext validationContext, String schemaPath, JsonNode schemaNode,
62+
public JsonSchema(ValidationContext validationContext, String schemaPath, URL currentUrl, JsonNode schemaNode,
4463
JsonSchema parent) {
45-
this(validationContext, schemaPath, schemaNode, parent, false);
64+
this(validationContext, schemaPath, currentUrl, schemaNode, parent, false);
4665
}
4766

48-
public JsonSchema(ValidationContext validationContext, String schemaPath, JsonNode schemaNode,
67+
public JsonSchema(ValidationContext validationContext, URL baseUrl, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
68+
this(validationContext, "#", baseUrl, schemaNode, null, suppressSubSchemaRetrieval);
69+
}
70+
71+
private JsonSchema(ValidationContext validationContext, String schemaPath, URL currentUrl, JsonNode schemaNode,
4972
JsonSchema parent, boolean suppressSubSchemaRetrieval) {
5073
super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval);
5174
this.validationContext = validationContext;
5275
this.config = validationContext.getConfig();
5376
this.validators = Collections.unmodifiableMap(this.read(schemaNode));
77+
this.currentUrl = this.combineCurrentUrlWithIds(currentUrl, schemaNode);
5478
}
55-
56-
public JsonSchema(ValidationContext validationContext, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
57-
this(validationContext, "#", schemaNode, null, suppressSubSchemaRetrieval);
79+
80+
private URL combineCurrentUrlWithIds(URL currentUrl, JsonNode schemaNode) {
81+
final JsonNode idNode = schemaNode.get("id");
82+
if (idNode == null) {
83+
return currentUrl;
84+
} else {
85+
try
86+
{
87+
return URLFactory.toURL(currentUrl, idNode.asText());
88+
}
89+
catch (MalformedURLException e)
90+
{
91+
throw new IllegalArgumentException(String.format("Invalid 'id' in schema: %s", schemaNode), e);
92+
}
93+
}
94+
}
95+
96+
public URL getCurrentUrl()
97+
{
98+
return this.currentUrl;
5899
}
59100

60101
/**

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static class Builder {
4242
private String defaultMetaSchemaURI;
4343
private Map<String, JsonMetaSchema> jsonMetaSchemas = new HashMap<String, JsonMetaSchema>();
4444
private Map<URL, URL> urlMap = new HashMap<URL, URL>();
45+
private URL rootSchemaUrl = null;
4546

4647
public Builder objectMapper(ObjectMapper objectMapper) {
4748
this.objectMapper = objectMapper;
@@ -75,14 +76,21 @@ public Builder addUrlMappings(Map<URL, URL> map) {
7576
return this;
7677
}
7778

79+
public Builder setRootSchemaUrl(URL rootSchemaUrl)
80+
{
81+
this.rootSchemaUrl = rootSchemaUrl;
82+
return this;
83+
}
84+
7885
public JsonSchemaFactory build() {
7986
// create builtin keywords with (custom) formats.
8087
return new JsonSchemaFactory(
8188
objectMapper == null ? new ObjectMapper() : objectMapper,
8289
urlFetcher == null ? new StandardURLFetcher(): urlFetcher,
8390
defaultMetaSchemaURI,
8491
jsonMetaSchemas,
85-
urlMap
92+
urlMap,
93+
rootSchemaUrl
8694
);
8795
}
8896
}
@@ -92,8 +100,14 @@ public JsonSchemaFactory build() {
92100
private final String defaultMetaSchemaURI;
93101
private final Map<String, JsonMetaSchema> jsonMetaSchemas;
94102
private final Map<URL, URL> urlMap;
103+
private final URL rootSchemaUrl;
95104

96-
private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String defaultMetaSchemaURI, Map<String, JsonMetaSchema> jsonMetaSchemas, Map<URL, URL> urlMap) {
105+
private JsonSchemaFactory(
106+
ObjectMapper mapper,
107+
URLFetcher urlFetcher,
108+
String defaultMetaSchemaURI,
109+
Map<String, JsonMetaSchema> jsonMetaSchemas, Map<URL, URL> urlMap,
110+
URL rootSchemaUrl) {
97111
if (mapper == null) {
98112
throw new IllegalArgumentException("ObjectMapper must not be null");
99113
}
@@ -117,6 +131,7 @@ private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String def
117131
this.urlFetcher = urlFetcher;
118132
this.jsonMetaSchemas = jsonMetaSchemas;
119133
this.urlMap = urlMap;
134+
this.rootSchemaUrl = rootSchemaUrl;
120135
}
121136

122137
/**
@@ -148,13 +163,14 @@ public static Builder builder(JsonSchemaFactory blueprint) {
148163
.urlFetcher(blueprint.urlFetcher)
149164
.defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
150165
.objectMapper(blueprint.mapper)
151-
.addUrlMappings(blueprint.urlMap);
166+
.addUrlMappings(blueprint.urlMap)
167+
.setRootSchemaUrl(blueprint.rootSchemaUrl);
152168
}
153169

154170
private JsonSchema newJsonSchema(JsonNode schemaNode, SchemaValidatorsConfig config) {
155171
final ValidationContext validationContext = createValidationContext(schemaNode);
156172
validationContext.setConfig(config);
157-
JsonSchema jsonSchema = new JsonSchema(validationContext, schemaNode);
173+
JsonSchema jsonSchema = new JsonSchema(validationContext, this.rootSchemaUrl, schemaNode);
158174
return jsonSchema;
159175
}
160176

@@ -213,8 +229,7 @@ public JsonSchema getSchema(URL schemaURL, SchemaValidatorsConfig config) {
213229
final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
214230

215231
if (idMatchesSourceUrl(jsonMetaSchema, schemaNode, schemaURL)) {
216-
217-
return new JsonSchema(new ValidationContext(jsonMetaSchema, this), schemaNode, true /*retrieved via id, resolving will not change anything*/);
232+
return new JsonSchema(new ValidationContext(jsonMetaSchema, this), mappedURL, schemaNode, true /*retrieved via id, resolving will not change anything*/);
218233
}
219234

220235
return newJsonSchema(schemaNode, config);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class NotValidator extends BaseJsonValidator implements JsonValidator {
3030

3131
public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
3232
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext);
33-
schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
33+
schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode, parentSchema);
3434

3535
parseErrorCode(getValidatorType().getErrorCodeKey());
3636
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
121121
int size = schemaNode.size();
122122
for (int i = 0; i < size; i++) {
123123
JsonNode childNode = schemaNode.get(i);
124-
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), childNode, parentSchema);
124+
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), childNode, parentSchema);
125125
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
126126
}
127127

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public PatternPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSc
3838
Iterator<String> names = schemaNode.fieldNames();
3939
while (names.hasNext()) {
4040
String name = names.next();
41-
schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, schemaNode.get(name), parentSchema));
41+
schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, parentSchema.getCurrentUrl(), schemaNode.get(name), parentSchema));
4242
}
4343
}
4444

0 commit comments

Comments
 (0)