summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Tay <49700559+justin-tay@users.noreply.github.com>2024-03-19 21:14:51 +0800
committerGitHub <noreply@github.com>2024-03-19 09:14:51 -0400
commitaeb48e25927c4f622f93f3accdc87de1f9c1daa1 (patch)
tree09d4020cb12fe9896c734a7db7b6f99d5190b943
parent3416e28db90696e5b091f7b8f99e00d93ad279fe (diff)
downloadjson-schema-validator-aeb48e25927c4f622f93f3accdc87de1f9c1daa1.tar.gz
Walk items schema instead of walking instance data (#993)
-rw-r--r--src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java2
-rw-r--r--src/main/java/com/networknt/schema/DynamicRefValidator.java16
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator.java160
-rw-r--r--src/main/java/com/networknt/schema/ItemsValidator202012.java21
-rw-r--r--src/main/java/com/networknt/schema/JsonValidator.java4
-rw-r--r--src/main/java/com/networknt/schema/PrefixItemsValidator.java50
-rw-r--r--src/main/java/com/networknt/schema/PropertiesValidator.java14
-rw-r--r--src/main/java/com/networknt/schema/RecursiveRefValidator.java16
-rw-r--r--src/main/java/com/networknt/schema/RefValidator.java16
-rw-r--r--src/test/java/com/networknt/schema/ItemsValidator202012Test.java88
-rw-r--r--src/test/java/com/networknt/schema/ItemsValidatorTest.java254
-rw-r--r--src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java124
-rw-r--r--src/test/java/com/networknt/schema/TypeValidatorTest.java11
-rw-r--r--src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java126
14 files changed, 839 insertions, 63 deletions
diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
index 62262ba..48b5f7d 100644
--- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -165,7 +165,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
@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);
}
diff --git a/src/main/java/com/networknt/schema/DynamicRefValidator.java b/src/main/java/com/networknt/schema/DynamicRefValidator.java
index 6f72993..7d68d23 100644
--- a/src/main/java/com/networknt/schema/DynamicRefValidator.java
+++ b/src/main/java/com/networknt/schema/DynamicRefValidator.java
@@ -113,6 +113,22 @@ public class DynamicRefValidator extends BaseJsonValidator {
.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);
}
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
index 7aede16..7912ebd 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -203,25 +203,125 @@ public class ItemsValidator extends BaseJsonValidator {
@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;
}
@@ -237,36 +337,14 @@ public class ItemsValidator extends BaseJsonValidator {
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);
}
diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java
index a9cd02a..9edcb37 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator202012.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java
@@ -122,6 +122,7 @@ public class ItemsValidator202012 extends BaseJsonValidator {
&& 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) {
@@ -131,10 +132,24 @@ public class ItemsValidator202012 extends BaseJsonValidator {
// 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;
@@ -150,7 +165,7 @@ public class ItemsValidator202012 extends BaseJsonValidator {
}
return result;
}
-
+
private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode,
JsonNodePath instanceLocation, boolean shouldValidateSchema, Set<ValidationMessage> validationMessages) {
//@formatter:off
diff --git a/src/main/java/com/networknt/schema/JsonValidator.java b/src/main/java/com/networknt/schema/JsonValidator.java
index fe4b71a..158a095 100644
--- a/src/main/java/com/networknt/schema/JsonValidator.java
+++ b/src/main/java/com/networknt/schema/JsonValidator.java
@@ -59,6 +59,10 @@ public interface JsonValidator extends JsonSchemaWalker {
@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();
}
diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
index 94bd136..ccda858 100644
--- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java
+++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java
@@ -47,8 +47,11 @@ public class PrefixItemsValidator extends BaseJsonValidator {
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.");
@@ -101,22 +104,49 @@ public class PrefixItemsValidator extends BaseJsonValidator {
@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;
}
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
index a47e61c..f378905 100644
--- a/src/main/java/com/networknt/schema/PropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -18,6 +18,7 @@ package com.networknt.schema;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.annotation.JsonNodeAnnotation;
import com.networknt.schema.utils.JsonSchemaRefs;
@@ -99,7 +100,7 @@ public class PropertiesValidator extends BaseJsonValidator {
if (errors == null) {
errors = new SetView<>();
}
- walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), false);
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
}
// reset the complex flag to the original value before the recursive call
@@ -118,7 +119,7 @@ public class PropertiesValidator extends BaseJsonValidator {
if (errors == null) {
errors = new SetView<>();
}
- walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), true);
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
}
// check whether the node which has not matched was mandatory or not
@@ -165,11 +166,12 @@ public class PropertiesValidator extends BaseJsonValidator {
applyPropertyDefaults((ObjectNode) node);
}
if (shouldValidateSchema) {
- validationMessages.union(validate(executionContext, node, rootNode, instanceLocation));
+ validationMessages.union(validate(executionContext, node == null ? MissingNode.getInstance() : node, rootNode,
+ instanceLocation));
} else {
WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner();
for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
- walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner, false);
+ walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner);
}
}
return validationMessages;
@@ -215,7 +217,7 @@ public class PropertiesValidator extends BaseJsonValidator {
private void walkSchema(ExecutionContext executionContext, Map.Entry<String, JsonSchema> entry, JsonNode node,
JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema,
- SetView<ValidationMessage> validationMessages, WalkListenerRunner propertyWalkListenerRunner, boolean skipWalk) {
+ SetView<ValidationMessage> validationMessages, WalkListenerRunner propertyWalkListenerRunner) {
JsonSchema propertySchema = entry.getValue();
JsonNode propertyNode = (node == null ? null : node.get(entry.getKey()));
JsonNodePath path = instanceLocation.append(entry.getKey());
@@ -226,7 +228,7 @@ public class PropertiesValidator extends BaseJsonValidator {
// Attempt to get the property node again in case the propertyNode was updated
propertyNode = node.get(entry.getKey());
}
- if (executeWalk && !(skipWalk && propertyNode == null)) {
+ if (executeWalk) {
validationMessages.union(
propertySchema.walk(executionContext, propertyNode, rootNode, path, shouldValidateSchema));
}
diff --git a/src/main/java/com/networknt/schema/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
index e73bb7d..8cd0de7 100644
--- a/src/main/java/com/networknt/schema/RecursiveRefValidator.java
+++ b/src/main/java/com/networknt/schema/RecursiveRefValidator.java
@@ -110,6 +110,22 @@ public class RecursiveRefValidator extends BaseJsonValidator {
.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);
}
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
index 1a32e6e..e6b803e 100644
--- a/src/main/java/com/networknt/schema/RefValidator.java
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -195,6 +195,22 @@ public class RefValidator extends BaseJsonValidator {
.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);
}
diff --git a/src/test/java/com/networknt/schema/ItemsValidator202012Test.java b/src/test/java/com/networknt/schema/ItemsValidator202012Test.java
index 401e182..f7330f3 100644
--- a/src/test/java/com/networknt/schema/ItemsValidator202012Test.java
+++ b/src/test/java/com/networknt/schema/ItemsValidator202012Test.java
@@ -18,12 +18,18 @@ package com.networknt.schema;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
/**
* ItemsValidatorTest.
@@ -55,4 +61,86 @@ public class ItemsValidator202012Test {
assertEquals("/1: string found, integer expected", message.getMessage());
assertNull(message.getProperty());
}
+
+ @Test
+ void walkNull() {
+ String schemaData = "{\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(1, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ }
+
+ @Test
+ void walkNullPrefixItems() {
+ String schemaData = "{\r\n"
+ + " \"prefixItems\": [\r\n"
+ + " {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " }\r\n"
+ + " ],\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(2, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(0).getKeyword());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("items", items.get(1).getKeyword());
+ }
}
diff --git a/src/test/java/com/networknt/schema/ItemsValidatorTest.java b/src/test/java/com/networknt/schema/ItemsValidatorTest.java
index 8823028..901dbc0 100644
--- a/src/test/java/com/networknt/schema/ItemsValidatorTest.java
+++ b/src/test/java/com/networknt/schema/ItemsValidatorTest.java
@@ -18,12 +18,22 @@ package com.networknt.schema;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
/**
* ItemsValidatorTest.
@@ -111,4 +121,248 @@ public class ItemsValidatorTest {
assertEquals(": index '1' is not defined in the schema and the schema does not allow additional items", message.getMessage());
assertNull(message.getProperty());
}
+
+ @Test
+ void walk() {
+ String schemaData = "{\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk("[\"the\",\"quick\",\"brown\"]", InputFormat.JSON, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(3, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ }
+
+ @Test
+ void walkNull() {
+ String schemaData = "{\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(1, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ }
+
+ @Test
+ void walkNullTupleItemsAdditional() {
+ String schemaData = "{\r\n"
+ + " \"items\": [\r\n"
+ + " {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n,"
+ + " {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " }\r\n"
+ + " ],\r\n"
+ + " \"additionalItems\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(3, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("items", items.get(0).getKeyword());
+ assertNull(items.get(0).getInstanceNode());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("items", items.get(1).getKeyword());
+ assertNull(items.get(1).getInstanceNode());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ assertEquals("additionalItems", items.get(2).getKeyword());
+ assertNull(items.get(2).getInstanceNode());
+ }
+
+ @Test
+ void walkTupleItemsAdditional() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"items\": [\r\n"
+ + " {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n,"
+ + " {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " }\r\n"
+ + " ],\r\n"
+ + " \"additionalItems\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ JsonNode input = JsonMapperFactory.getInstance().readTree("[\"hello\"]");
+ ValidationResult result = schema.walk(input, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(3, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("items", items.get(0).getKeyword());
+ assertEquals("hello", items.get(0).getInstanceNode().textValue());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("items", items.get(1).getKeyword());
+ assertNull(items.get(1).getInstanceNode());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ assertEquals("additionalItems", items.get(2).getKeyword());
+ assertNull(items.get(2).getInstanceNode());
+ }
+
+ @Test
+ void walkTupleItemsAdditionalDefaults() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\r\n"
+ + " \"items\": [\r\n"
+ + " {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"1\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"type\": \"integer\",\r\n"
+ + " \"default\": 2\r\n"
+ + " }\r\n"
+ + " ],\r\n"
+ + " \"additionalItems\": {\r\n"
+ + " \"type\": \"string\",\r\n"
+ + " \"default\": \"additional\"\r\n"
+ + " }\r\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ JsonNode input = JsonMapperFactory.getInstance().readTree("[null, null, null, null]");
+ ValidationResult result = schema.walk(input, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(4, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("items", items.get(0).getKeyword());
+ assertEquals("1", items.get(0).getInstanceNode().textValue());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("items", items.get(1).getKeyword());
+ assertEquals(2, items.get(1).getInstanceNode().intValue());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ assertEquals("additionalItems", items.get(2).getKeyword());
+ assertEquals("additional", items.get(2).getInstanceNode().asText());
+ assertEquals("/3", items.get(3).getInstanceLocation().toString());
+ assertEquals("additionalItems", items.get(3).getKeyword());
+ assertEquals("additional", items.get(3).getInstanceNode().asText());
+ }
}
diff --git a/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java b/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java
index b57d822..a06eda4 100644
--- a/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java
+++ b/src/test/java/com/networknt/schema/PrefixItemsValidatorTest.java
@@ -4,8 +4,17 @@ import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.Test;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.SpecVersion.VersionFlag;
+import com.networknt.schema.serialization.JsonMapperFactory;
+import com.networknt.schema.walk.JsonSchemaWalkListener;
+import com.networknt.schema.walk.WalkEvent;
+import com.networknt.schema.walk.WalkFlow;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
@@ -54,8 +63,8 @@ public class PrefixItemsValidatorTest extends AbstractJsonSchemaTestSuite {
Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
assertFalse(messages.isEmpty());
ValidationMessage message = messages.iterator().next();
- assertEquals("/prefixItems/type", message.getEvaluationPath().toString());
- assertEquals("https://www.example.org/schema#/prefixItems/type", message.getSchemaLocation().toString());
+ assertEquals("/prefixItems/0/type", message.getEvaluationPath().toString());
+ assertEquals("https://www.example.org/schema#/prefixItems/0/type", message.getSchemaLocation().toString());
assertEquals("/0", message.getInstanceLocation().toString());
assertEquals("\"string\"", message.getSchemaNode().toString());
assertEquals("1", message.getInstanceNode().toString());
@@ -109,4 +118,115 @@ public class PrefixItemsValidatorTest extends AbstractJsonSchemaTestSuite {
assertEquals(": index '2' is not defined in the schema and the schema does not allow additional items", message.getMessage());
assertNull(message.getProperty());
}
+
+ @Test
+ void walkNull() {
+ String schemaData = "{\n"
+ + " \"prefixItems\": [\n"
+ + " {\n"
+ + " \"type\": \"integer\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"string\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"integer\"\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(3, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(0).getKeyword());
+ assertEquals("#/prefixItems/0", items.get(0).getSchema().getSchemaLocation().toString());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(1).getKeyword());
+ assertEquals("#/prefixItems/1", items.get(1).getSchema().getSchemaLocation().toString());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(2).getKeyword());
+ assertEquals("#/prefixItems/2", items.get(2).getSchema().getSchemaLocation().toString());
+ }
+
+ @Test
+ void walkDefaults() throws JsonMappingException, JsonProcessingException {
+ String schemaData = "{\n"
+ + " \"prefixItems\": [\n"
+ + " {\n"
+ + " \"type\": \"integer\",\n"
+ + " \"default\": 1\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"string\",\n"
+ + " \"default\": \"2\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"type\": \"integer\",\n"
+ + " \"default\": 3\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setPathType(PathType.JSON_POINTER);
+ config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true));
+ config.addItemWalkListener(new JsonSchemaWalkListener() {
+
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ });
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ JsonNode input = JsonMapperFactory.getInstance().readTree("[null, null]");
+ ValidationResult result = schema.walk(input, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(3, items.size());
+ assertEquals("/0", items.get(0).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(0).getKeyword());
+ assertEquals("#/prefixItems/0", items.get(0).getSchema().getSchemaLocation().toString());
+ assertEquals("/1", items.get(1).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(1).getKeyword());
+ assertEquals("#/prefixItems/1", items.get(1).getSchema().getSchemaLocation().toString());
+ assertEquals("/2", items.get(2).getInstanceLocation().toString());
+ assertEquals("prefixItems", items.get(2).getKeyword());
+ assertEquals("#/prefixItems/2", items.get(2).getSchema().getSchemaLocation().toString());
+ }
}
diff --git a/src/test/java/com/networknt/schema/TypeValidatorTest.java b/src/test/java/com/networknt/schema/TypeValidatorTest.java
index 8560b56..487b814 100644
--- a/src/test/java/com/networknt/schema/TypeValidatorTest.java
+++ b/src/test/java/com/networknt/schema/TypeValidatorTest.java
@@ -16,6 +16,7 @@
package com.networknt.schema;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Set;
@@ -139,4 +140,14 @@ public class TypeValidatorTest {
messages = schema.validate("2.000001", InputFormat.JSON);
assertEquals(1, messages.size());
}
+
+ @Test
+ void walkNull() {
+ String schemaData = "{\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + "}";
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V4).getSchema(schemaData);
+ ValidationResult result = schema.walk(null, true);
+ assertTrue(result.getValidationMessages().isEmpty());
+ }
}
diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
index d4b6701..af19ac0 100644
--- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
+++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java
@@ -38,6 +38,7 @@ import com.networknt.schema.ApplyDefaultsStrategy;
import com.networknt.schema.InputFormat;
import com.networknt.schema.ItemsValidator;
import com.networknt.schema.ItemsValidator202012;
+import com.networknt.schema.JsonNodePath;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.JsonSchemaRef;
@@ -811,4 +812,129 @@ class JsonSchemaWalkListenerTest {
assertEquals("{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"}", inputNode.toString());
assertTrue(result.getValidationMessages().isEmpty());
}
+
+ /**
+ * Issue 989
+ */
+ @Test
+ void itemListenerDraft201909() {
+ String schemaData = " {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"children\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }";
+ JsonSchemaWalkListener listener = new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ };
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addItemWalkListener(listener);
+ config.addPropertyWalkListener(listener);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909).getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(4, items.size());
+ assertEquals("$.name", items.get(0).getInstanceLocation().toString());
+ assertEquals("properties", items.get(0).getKeyword());
+ assertEquals("#/properties/name", items.get(0).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children[0].name", items.get(1).getInstanceLocation().toString());
+ assertEquals("properties", items.get(1).getKeyword());
+ assertEquals("#/properties/children/items/properties/name", items.get(1).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children[0]", items.get(2).getInstanceLocation().toString());
+ assertEquals("items", items.get(2).getKeyword());
+ assertEquals("#/properties/children/items", items.get(2).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children", items.get(3).getInstanceLocation().toString());
+ assertEquals("properties", items.get(3).getKeyword());
+ assertEquals("#/properties/children", items.get(3).getSchema().getSchemaLocation().toString());
+ }
+
+ /**
+ * Issue 989
+ */
+ @Test
+ void itemListenerDraft202012() {
+ String schemaData = " {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " },\r\n"
+ + " \"children\": {\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }";
+ JsonSchemaWalkListener listener = new JsonSchemaWalkListener() {
+ @Override
+ public WalkFlow onWalkStart(WalkEvent walkEvent) {
+ return WalkFlow.CONTINUE;
+ }
+
+ @Override
+ public void onWalkEnd(WalkEvent walkEvent, Set<ValidationMessage> validationMessages) {
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) walkEvent.getExecutionContext()
+ .getCollectorContext()
+ .getCollectorMap()
+ .computeIfAbsent("items", key -> new ArrayList<JsonNodePath>());
+ items.add(walkEvent);
+ }
+ };
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.addItemWalkListener(listener);
+ config.addPropertyWalkListener(listener);
+ JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+ ValidationResult result = schema.walk(null, true);
+ @SuppressWarnings("unchecked")
+ List<WalkEvent> items = (List<WalkEvent>) result.getExecutionContext().getCollectorContext().get("items");
+ assertEquals(4, items.size());
+ assertEquals("$.name", items.get(0).getInstanceLocation().toString());
+ assertEquals("properties", items.get(0).getKeyword());
+ assertEquals("#/properties/name", items.get(0).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children[0].name", items.get(1).getInstanceLocation().toString());
+ assertEquals("properties", items.get(1).getKeyword());
+ assertEquals("#/properties/children/items/properties/name", items.get(1).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children[0]", items.get(2).getInstanceLocation().toString());
+ assertEquals("items", items.get(2).getKeyword());
+ assertEquals("#/properties/children/items", items.get(2).getSchema().getSchemaLocation().toString());
+ assertEquals("$.children", items.get(3).getInstanceLocation().toString());
+ assertEquals("properties", items.get(3).getKeyword());
+ assertEquals("#/properties/children", items.get(3).getSchema().getSchemaLocation().toString());
+ }
+
}