Skip to content
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

Walk items schema instead of walking instance data #993

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading