Skip to content

Commit

Permalink
Walk items schema instead of walking instance data
Browse files Browse the repository at this point in the history
  • Loading branch information
justin-tay committed Mar 19, 2024
1 parent 3416e28 commit 05e5fd0
Show file tree
Hide file tree
Showing 14 changed files with 839 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

@Override
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
if (shouldValidateSchema) {
if (shouldValidateSchema && node != null) {
return validate(executionContext, node, rootNode, instanceLocation);
}

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/networknt/schema/DynamicRefValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
.arguments(schemaNode.asText()).build();
throw new InvalidSchemaRefException(validationMessage);
}
if (node == null) {
// Check for circular dependency
SchemaLocation schemaLocation = refSchema.getSchemaLocation();
JsonSchema check = refSchema;
boolean circularDependency = false;
while (check.getEvaluationParentSchema() != null) {
check = check.getEvaluationParentSchema();
if (check.getSchemaLocation().equals(schemaLocation)) {
circularDependency = true;
break;
}
}
if (circularDependency) {
return Collections.emptySet();
}
}
return refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
}

Expand Down
160 changes: 119 additions & 41 deletions src/main/java/com/networknt/schema/ItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -203,25 +203,125 @@ private boolean doValidate(ExecutionContext executionContext, SetView<Validation

@Override
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
HashSet<ValidationMessage> validationMessages = new LinkedHashSet<>();
if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) node;
JsonNode defaultNode = null;
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
&& this.schema != null) {
defaultNode = getDefaultNode(this.schema);
Set<ValidationMessage> validationMessages = new LinkedHashSet<>();
boolean collectAnnotations = collectAnnotations();

// Add items annotation
if (collectAnnotations || collectAnnotations(executionContext)) {
if (this.schema != null) {
// Applies to all
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(true).build());
} else if (this.tupleSchema != null) {
// Tuples
int items = node.isArray() ? node.size() : 1;
int schemas = this.tupleSchema.size();
if (items > schemas) {
// More items than schemas so the keyword only applied to the number of schemas
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(schemas).build());
} else {
// Applies to all
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(true).build());
}
}
int i = 0;
for (JsonNode n : arrayNode) {
if (n.isNull() && defaultNode != null) {
arrayNode.set(i, defaultNode);
n = defaultNode;
}

if (this.schema != null) {
// Walk the schema.
if (node instanceof ArrayNode) {
int count = Math.max(1, node.size());
ArrayNode arrayNode = (ArrayNode) node;
JsonNode defaultNode = null;
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) {
defaultNode = getDefaultNode(this.schema);
}
for (int i = 0; i < count; i++) {
JsonNode n = arrayNode.get(i);
if (n != null) {
if (n.isNull() && defaultNode != null) {
arrayNode.set(i, defaultNode);
n = defaultNode;
}
}
walkSchema(executionContext, this.schema, n, rootNode, instanceLocation.append(i), shouldValidateSchema, validationMessages, ValidatorTypeCode.ITEMS.getValue());
}
} else {
walkSchema(executionContext, this.schema, null, rootNode, instanceLocation.append(0), shouldValidateSchema, validationMessages, ValidatorTypeCode.ITEMS.getValue());
}
}
else if (this.tupleSchema != null) {
int prefixItems = this.tupleSchema.size();
for (int i = 0; i < prefixItems; i++) {
// walk tuple schema
if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) node;
JsonNode defaultNode = null;
JsonNode n = arrayNode.get(i);
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) {
defaultNode = getDefaultNode(this.tupleSchema.get(i));
}
if (n != null) {
if (n.isNull() && defaultNode != null) {
arrayNode.set(i, defaultNode);
n = defaultNode;
}
}
walkSchema(executionContext, this.tupleSchema.get(i), n, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages, ValidatorTypeCode.ITEMS.getValue());
} else {
walkSchema(executionContext, this.tupleSchema.get(i), null, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages, ValidatorTypeCode.ITEMS.getValue());
}
}
if (this.additionalSchema != null) {
boolean hasAdditionalItem = false;

int additionalItems = Math.max(1, (node != null ? node.size() : 0) - prefixItems);
for (int x = 0; x < additionalItems; x++) {
int i = x + prefixItems;
// walk additional item schema
if (node instanceof ArrayNode) {
ArrayNode arrayNode = (ArrayNode) node;
JsonNode defaultNode = null;
JsonNode n = arrayNode.get(i);
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) {
defaultNode = getDefaultNode(this.additionalSchema);
}
if (n != null) {
if (n.isNull() && defaultNode != null) {
arrayNode.set(i, defaultNode);
n = defaultNode;
}
}
walkSchema(executionContext, this.additionalSchema, n, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages, PROPERTY_ADDITIONAL_ITEMS);
if (n != null) {
hasAdditionalItem = true;
}
} else {
walkSchema(executionContext, this.additionalSchema, null, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages, PROPERTY_ADDITIONAL_ITEMS);
}
}

if (hasAdditionalItem) {
if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) {
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.additionalItemsEvaluationPath)
.schemaLocation(this.additionalItemsSchemaLocation)
.keyword("additionalItems").value(true).build());
}
}
doWalk(executionContext, validationMessages, i, n, rootNode, instanceLocation, shouldValidateSchema);
i++;
}
} else {
doWalk(executionContext, validationMessages, 0, node, rootNode, instanceLocation, shouldValidateSchema);
}
return validationMessages;
}
Expand All @@ -237,36 +337,14 @@ private static JsonNode getDefaultNode(JsonSchema schema) {
return result;
}

private void doWalk(ExecutionContext executionContext, HashSet<ValidationMessage> validationMessages, int i, JsonNode node,
JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
if (this.schema != null) {
// Walk the schema.
walkSchema(executionContext, this.schema, node, rootNode, instanceLocation.append(i), shouldValidateSchema, validationMessages);
}

if (this.tupleSchema != null) {
if (i < this.tupleSchema.size()) {
// walk tuple schema
walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages);
} else {
if (this.additionalSchema != null) {
// walk additional item schema
walkSchema(executionContext, this.additionalSchema, node, rootNode, instanceLocation.append(i),
shouldValidateSchema, validationMessages);
}
}
}
}

private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(),
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages, String keyword) {
boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, keyword,
node, rootNode, instanceLocation, walkSchema, this);
if (executeWalk) {
validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema));
}
this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode,
this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, keyword, node, rootNode,
instanceLocation, walkSchema, this, validationMessages);

}
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/com/networknt/schema/ItemsValidator202012.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
&& this.schema != null) {
defaultNode = getDefaultNode(this.schema);
}
boolean evaluated = false;
for (int i = this.prefixCount; i < node.size(); ++i) {
JsonNode n = node.get(i);
if (n.isNull() && defaultNode != null) {
Expand All @@ -131,10 +132,24 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
// Walk the schema.
walkSchema(executionContext, this.schema, n, rootNode, instanceLocation.append(i), shouldValidateSchema,
validationMessages);
if (n != null) {
evaluated = true;
}
}
if (evaluated) {
if (collectAnnotations() || collectAnnotations(executionContext)) {
// Applies to all
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(true).build());
}
}
} else {
walkSchema(executionContext, this.schema, node, rootNode, instanceLocation, shouldValidateSchema,
validationMessages);
// If the node is not an ArrayNode, eg. ObjectNode or null then the instance is null.
// The instance location starts at the end of the prefix count.
walkSchema(executionContext, this.schema, null, rootNode, instanceLocation.append(this.prefixCount),
shouldValidateSchema, validationMessages);
}

return validationMessages;
Expand All @@ -150,7 +165,7 @@ private static JsonNode getDefaultNode(JsonSchema schema) {
}
return result;
}

private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
//@formatter:off
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/networknt/schema/JsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ default void preloadJsonSchema() throws JsonSchemaException {
@Override
default Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema) {
if (node == null) {
// Note that null is not the same as NullNode
return Collections.emptySet();
}
return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation)
: Collections.emptySet();
}
Expand Down
50 changes: 40 additions & 10 deletions src/main/java/com/networknt/schema/PrefixItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ public PrefixItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluati
this.tupleSchema = new ArrayList<>();

if (schemaNode instanceof ArrayNode && 0 < schemaNode.size()) {
int i = 0;
for (JsonNode s : schemaNode) {
this.tupleSchema.add(validationContext.newSchema(schemaLocation, evaluationPath, s, parentSchema));
this.tupleSchema.add(validationContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), s,
parentSchema));
i++;
}
} else {
throw new IllegalArgumentException("The value of 'prefixItems' MUST be a non-empty array of valid JSON Schemas.");
Expand Down Expand Up @@ -101,22 +104,49 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
@Override
public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
Set<ValidationMessage> validationMessages = new LinkedHashSet<>();

if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()
&& node.isArray()) {
if (node instanceof ArrayNode) {
ArrayNode array = (ArrayNode) node;
int count = Math.min(node.size(), this.tupleSchema.size());
int count = this.tupleSchema.size();
for (int i = 0; i < count; ++i) {
JsonNode n = node.get(i);
JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i));
if (n.isNull() && defaultNode != null) {
array.set(i, defaultNode);
n = defaultNode;
if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) {
JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i));
if (n != null) {
// Defaults only set if array index is explicitly null
if (n.isNull() && defaultNode != null) {
array.set(i, defaultNode);
n = defaultNode;
}
}
}
doWalk(executionContext, validationMessages, i, n, rootNode, instanceLocation, shouldValidateSchema);
}
}

// Add annotation
if (collectAnnotations() || collectAnnotations(executionContext)) {
// Tuples
int items = node.isArray() ? node.size() : 1;
int schemas = this.tupleSchema.size();
if (items > schemas) {
// More items than schemas so the keyword only applied to the number of schemas
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(schemas).build());
} else {
// Applies to all
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation)
.evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation)
.keyword(getKeyword()).value(true).build());
}
}
} else {
int count = this.tupleSchema.size();
for (int i = 0; i < count; ++i) {
doWalk(executionContext, validationMessages, i, null, rootNode, instanceLocation, shouldValidateSchema);
}
}
return validationMessages;
}

Expand Down
Loading

0 comments on commit 05e5fd0

Please sign in to comment.