Skip to content

Commit

Permalink
Merge pull request #53 from joblift/optimized-oneof
Browse files Browse the repository at this point in the history
Optimization for OneOf
  • Loading branch information
stevehu authored Dec 10, 2017
2 parents 3d52d67 + 96d26bc commit bdfb117
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 19 deletions.
111 changes: 103 additions & 8 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,113 @@

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;

public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class);

private List<JsonSchema> schemas = new ArrayList<JsonSchema>();
private List<ShortcutValidator> schemas = new ArrayList<ShortcutValidator>();

private static class ShortcutValidator {
private final JsonSchema schema;
private final Map<String, String> constants;

ShortcutValidator(JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext, JsonSchema schema) {
JsonNode refNode = schemaNode.get(ValidatorTypeCode.REF.getValue());
JsonSchema resolvedRefSchema = refNode != null && refNode.isTextual() ? RefValidator.getRefSchema(parentSchema, validationContext,refNode.textValue()) : null;
this.constants = extractConstants(schemaNode, resolvedRefSchema);
this.schema = schema;
}

private Map<String, String> extractConstants(JsonNode schemaNode, JsonSchema resolvedRefSchema) {
Map<String, String> refMap = resolvedRefSchema != null ? extractConstants(resolvedRefSchema.getSchemaNode()) : Collections.<String,String>emptyMap();
Map<String, String> schemaMap = extractConstants(schemaNode);
if (refMap.isEmpty() ) {
return schemaMap;
}
if (schemaMap.isEmpty()) {
return refMap;
}
Map<String, String> joined = new HashMap<String, String>();
joined.putAll(schemaMap);
joined.putAll(refMap);
return joined;
}

private Map<String, String> extractConstants(JsonNode schemaNode) {
Map<String, String> result = new HashMap<String, String>();
if (!schemaNode.isObject()) {
return result;
}

JsonNode propertiesNode = schemaNode.get("properties");
if (propertiesNode == null || !propertiesNode.isObject()) {
return result;
}
Iterator<String> fit = propertiesNode.fieldNames();
while (fit.hasNext()) {
String fieldName = fit.next();
JsonNode jsonNode = propertiesNode.get(fieldName);
String constantFieldValue = getConstantFieldValue(jsonNode);
if (constantFieldValue != null && !constantFieldValue.isEmpty()) {
result.put(fieldName, constantFieldValue);
}
}
return result;
}
private String getConstantFieldValue(JsonNode jsonNode) {
if (jsonNode == null || !jsonNode.isObject() || !jsonNode.has("enum")) {
return null;
}
JsonNode enumNode = jsonNode.get("enum");
if (enumNode == null || !enumNode.isArray() ) {
return null;
}
if (enumNode.size() != 1) {
return null;
}
JsonNode valueNode = enumNode.get(0);
if (valueNode == null || !valueNode.isTextual()) {
return null;
}
return valueNode.textValue();
}

public boolean allConstantsMatch(JsonNode node) {
for (Map.Entry<String, String> e: constants.entrySet()) {
JsonNode valueNode = node.get(e.getKey());
if (valueNode != null && valueNode.isTextual()) {
boolean match = e.getValue().equals(valueNode.textValue());
if (!match) {
return false;
}
}
}
return true;
}

}

public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext);
int size = schemaNode.size();
for (int i = 0; i < size; i++) {
schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
JsonNode childNode = schemaNode.get(i);
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), childNode, parentSchema);
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
}

parseErrorCode(getValidatorType().getErrorCodeKey());
Expand All @@ -49,7 +134,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
int numberOfValidSchema = 0;
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();

for (JsonSchema schema : schemas) {
for (ShortcutValidator validator : schemas) {
if (!validator.allConstantsMatch(node)) {
// take a shortcut: if there is any constant that does not match,
// we can bail out
continue;
}
JsonSchema schema = validator.schema;
Set<ValidationMessage> schemaErrors = schema.validate(node, rootNode, at);
if (schemaErrors.isEmpty()) {
numberOfValidSchema++;
Expand All @@ -70,6 +161,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
if (ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(msg.getType())) {
it.remove();
}
if (errors.isEmpty()) {
// ensure there is always an error reported if number of valid schemas is 0
errors.add(buildValidationMessage(at, ""));
}
}
}
if (numberOfValidSchema > 1) {
Expand Down
27 changes: 16 additions & 11 deletions src/main/java/com/networknt/schema/RefValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,21 @@ public class RefValidator extends BaseJsonValidator implements JsonValidator {

protected JsonSchema schema;

private final String REF_DOMAIN = "/";
private final String REF_CURRENT = "#";
private final String REF_RELATIVE = "../";
private static final String REF_DOMAIN = "/";
private static final String REF_CURRENT = "#";
private static final String REF_RELATIVE = "../";

public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {

super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext);
String refValue = schemaNode.asText();
schema = getRefSchema(parentSchema, validationContext, refValue);
if (schema == null) {
throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.REF.getValue(), CustomErrorMessageType.of("internal.unresolvedRef", new MessageFormat("{0}: Reference {1} cannot be resolved")), schemaPath, refValue));
}
}

static JsonSchema getRefSchema(JsonSchema parentSchema, ValidationContext validationContext, String refValue) {
if (!refValue.startsWith(REF_CURRENT)) {
// handle remote ref
String schemaUrl = refValue;
Expand All @@ -61,29 +68,27 @@ public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSch
parentSchema = validationContext.getJsonSchemaFactory().getSchema(is);
}
if (index < 0) {
schema = parentSchema.findAncestor();
return parentSchema.findAncestor();
} else {
refValue = refValue.substring(index);
}
}
if (refValue.equals(REF_CURRENT)) {
schema = parentSchema.findAncestor();
return parentSchema.findAncestor();
} else {
JsonNode node = parentSchema.getRefSchemaNode(refValue);
if (node != null) {
schema = new JsonSchema(validationContext, refValue, node, parentSchema);
return new JsonSchema(validationContext, refValue, node, parentSchema);
}
}
if (schema == null) {
throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.REF.getValue(), CustomErrorMessageType.of("internal.unresolvedRef", new MessageFormat("{0}: Reference {1} cannot be resolved")), schemaPath, refValue));
}
return null;
}

private boolean isRelativePath(String schemaUrl) {
private static boolean isRelativePath(String schemaUrl) {
return !schemaUrl.startsWith("http");
}

private String obtainAbsolutePath(JsonSchema parentSchema, String schemaUrl) {
private static String obtainAbsolutePath(JsonSchema parentSchema, String schemaUrl) {
String baseSchemaUrl = parentSchema.findAncestor().getSchemaNode().get("id").textValue();
int index = baseSchemaUrl.lastIndexOf("/");
baseSchemaUrl = baseSchemaUrl.substring(0, index);
Expand Down

0 comments on commit bdfb117

Please sign in to comment.