Skip to content

Improved Ref Validator #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 7, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, Jso
additionalPropertiesSchema = null;
} else if (schemaNode.isObject()) {
allowAdditionalProperties = true;
additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode, parentSchema);
} else {
allowAdditionalProperties = false;
additionalPropertiesSchema = null;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext);
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode.get(i), parentSchema));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext);
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode.get(i), parentSchema));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema
depsProps.add(pvalue.get(i).asText());
}
} else if (pvalue.isObject()) {
schemaDeps.put(pname, new JsonSchema(validationContext, pname, pvalue, parentSchema));
schemaDeps.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUrl(), pvalue, parentSchema));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/networknt/schema/ItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
if (schemaNode.isObject()) {
schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), schemaNode, parentSchema);
} else {
tupleSchema = new ArrayList<JsonSchema>();
for (JsonNode s : schemaNode) {
tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), s, parentSchema));
tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), s, parentSchema));
}

JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS);
if (addItemNode != null) {
if (addItemNode.isBoolean()) {
additionalItems = addItemNode.asBoolean();
} else if (addItemNode.isObject()) {
additionalSchema = new JsonSchema(validationContext, addItemNode);
additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUrl(), addItemNode);
}
}
}
Expand Down
63 changes: 52 additions & 11 deletions src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.url.URLFactory;

/**
* This is the core of json constraint implementation. It parses json constraint
* file and generates JsonValidators. The class is thread safe, once it is
Expand All @@ -34,27 +42,60 @@ public class JsonSchema extends BaseJsonValidator {
protected final Map<String, JsonValidator> validators;
private final ValidationContext validationContext;

/**
* This is the current url of this schema. This url could refer to the url of this schema's file
* or it could potentially be a url that has been altered by an id. An 'id' is able to completely overwrite
* the current url or add onto it. This is necessary so that '$ref's are able to be relative to a
* combination of the current schema file's url and 'id' urls visible to this schema.
*
* This can be null. If it is null, then the creation of relative urls will fail. However, an absolute
* 'id' would still be able to specify an absolute url.
*/
private final URL currentUrl;

private JsonValidator requiredValidator = null;

public JsonSchema(ValidationContext validationContext, JsonNode schemaNode) {
this(validationContext, "#", schemaNode, null);
public JsonSchema(ValidationContext validationContext, URL baseUrl, JsonNode schemaNode) {
this(validationContext, "#", baseUrl, schemaNode, null);
}

public JsonSchema(ValidationContext validationContext, String schemaPath, JsonNode schemaNode,
public JsonSchema(ValidationContext validationContext, String schemaPath, URL currentUrl, JsonNode schemaNode,
JsonSchema parent) {
this(validationContext, schemaPath, schemaNode, parent, false);
this(validationContext, schemaPath, currentUrl, schemaNode, parent, false);
}

public JsonSchema(ValidationContext validationContext, String schemaPath, JsonNode schemaNode,
public JsonSchema(ValidationContext validationContext, URL baseUrl, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
this(validationContext, "#", baseUrl, schemaNode, null, suppressSubSchemaRetrieval);
}

private JsonSchema(ValidationContext validationContext, String schemaPath, URL currentUrl, JsonNode schemaNode,
JsonSchema parent, boolean suppressSubSchemaRetrieval) {
super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval);
this.validationContext = validationContext;
this.config = validationContext.getConfig();
this.currentUrl = this.combineCurrentUrlWithIds(currentUrl, schemaNode);
this.validators = Collections.unmodifiableMap(this.read(schemaNode));
}

public JsonSchema(ValidationContext validationContext, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
this(validationContext, "#", schemaNode, null, suppressSubSchemaRetrieval);

private URL combineCurrentUrlWithIds(URL currentUrl, JsonNode schemaNode) {
final JsonNode idNode = schemaNode.get("id");
if (idNode == null) {
return currentUrl;
} else {
try
{
return URLFactory.toURL(currentUrl, idNode.asText());
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException(String.format("Invalid 'id' in schema: %s", schemaNode), e);
}
}
}

public URL getCurrentUrl()
{
return this.currentUrl;
}

/**
Expand Down
41 changes: 27 additions & 14 deletions src/main/java/com/networknt/schema/JsonSchemaFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ public JsonSchemaFactory build() {
private final Map<String, JsonMetaSchema> jsonMetaSchemas;
private final Map<String, String> urlMap;

private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String defaultMetaSchemaURI, Map<String, JsonMetaSchema> jsonMetaSchemas, Map<String, String> urlMap) {
private JsonSchemaFactory(
ObjectMapper mapper,
URLFetcher urlFetcher,
String defaultMetaSchemaURI,
Map<String, JsonMetaSchema> jsonMetaSchemas,
Map<String, String> urlMap) {
if (mapper == null) {
throw new IllegalArgumentException("ObjectMapper must not be null");
}
Expand Down Expand Up @@ -152,10 +157,10 @@ public static Builder builder(JsonSchemaFactory blueprint) {
.addUrlMappings(blueprint.urlMap);
}

private JsonSchema newJsonSchema(JsonNode schemaNode, SchemaValidatorsConfig config) {
private JsonSchema newJsonSchema(URL schemaUrl, JsonNode schemaNode, SchemaValidatorsConfig config) {
final ValidationContext validationContext = createValidationContext(schemaNode);
validationContext.setConfig(config);
JsonSchema jsonSchema = new JsonSchema(validationContext, schemaNode);
JsonSchema jsonSchema = new JsonSchema(validationContext, schemaUrl, schemaNode);
return jsonSchema;
}

Expand All @@ -173,11 +178,11 @@ private JsonMetaSchema findMetaSchemaForSchema(JsonNode schemaNode) {
}
return jsonMetaSchema;
}

public JsonSchema getSchema(String schema, SchemaValidatorsConfig config) {
try {
final JsonNode schemaNode = mapper.readTree(schema);
return newJsonSchema(schemaNode, config);
return newJsonSchema(null, schemaNode, config);
} catch (IOException ioe) {
logger.error("Failed to load json schema!", ioe);
throw new JsonSchemaException(ioe);
Expand All @@ -187,11 +192,11 @@ public JsonSchema getSchema(String schema, SchemaValidatorsConfig config) {
public JsonSchema getSchema(String schema) {
return getSchema(schema, null);
}

public JsonSchema getSchema(InputStream schemaStream, SchemaValidatorsConfig config) {
try {
final JsonNode schemaNode = mapper.readTree(schemaStream);
return newJsonSchema(schemaNode, config);
return newJsonSchema(null, schemaNode, config);
} catch (IOException ioe) {
logger.error("Failed to load json schema!", ioe);
throw new JsonSchemaException(ioe);
Expand All @@ -201,7 +206,7 @@ public JsonSchema getSchema(InputStream schemaStream, SchemaValidatorsConfig con
public JsonSchema getSchema(InputStream schemaStream) {
return getSchema(schemaStream, null);
}

public JsonSchema getSchema(URL schemaURL, SchemaValidatorsConfig config) {
try {
InputStream inputStream = null;
Expand All @@ -214,11 +219,10 @@ public JsonSchema getSchema(URL schemaURL, SchemaValidatorsConfig config) {
final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);

if (idMatchesSourceUrl(jsonMetaSchema, schemaNode, schemaURL)) {

return new JsonSchema(new ValidationContext(jsonMetaSchema, this), schemaNode, true /*retrieved via id, resolving will not change anything*/);
return new JsonSchema(new ValidationContext(jsonMetaSchema, this), mappedURL, schemaNode, true /*retrieved via id, resolving will not change anything*/);
}

return newJsonSchema(schemaNode, config);
return newJsonSchema(mappedURL, schemaNode, config);
} finally {
if (inputStream != null) {
inputStream.close();
Expand All @@ -231,15 +235,24 @@ public JsonSchema getSchema(URL schemaURL, SchemaValidatorsConfig config) {
}

public JsonSchema getSchema(URL schemaURL) {
return getSchema(schemaURL, null);
return getSchema(schemaURL, new SchemaValidatorsConfig());
}

public JsonSchema getSchema(URL schemaUrl, JsonNode jsonNode, SchemaValidatorsConfig config) {
return newJsonSchema(schemaUrl, jsonNode, config);
}


public JsonSchema getSchema(JsonNode jsonNode, SchemaValidatorsConfig config) {
return newJsonSchema(jsonNode, config);
return newJsonSchema(null, jsonNode, config);
}

public JsonSchema getSchema(URL schemaUrl, JsonNode jsonNode) {
return newJsonSchema(schemaUrl, jsonNode, null);
}

public JsonSchema getSchema(JsonNode jsonNode) {
return newJsonSchema(jsonNode, null);
return newJsonSchema(null, jsonNode, null);
}

private boolean idMatchesSourceUrl(JsonMetaSchema metaSchema, JsonNode schema, URL schemaUrl) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/NotValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class NotValidator extends BaseJsonValidator implements JsonValidator {

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

parseErrorCode(getValidatorType().getErrorCodeKey());
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
JsonNode childNode = schemaNode.get(i);
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), childNode, parentSchema);
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUrl(), childNode, parentSchema);
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public PatternPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSc
Iterator<String> names = schemaNode.fieldNames();
while (names.hasNext()) {
String name = names.next();
schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, schemaNode.get(name), parentSchema));
schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, parentSchema.getCurrentUrl(), schemaNode.get(name), parentSchema));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema pa
schemas = new HashMap<String, JsonSchema>();
for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
String pname = it.next();
schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, schemaNode.get(pname), parentSchema));
schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUrl(), schemaNode.get(pname), parentSchema));
}
}

Expand Down
Loading