From a4b187eac15589665948e7a4b7c54563016896c3 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:12:11 +0800 Subject: [PATCH 01/13] Allow apply defaults to resolve ref and refactor walk listeners --- .../com/networknt/schema/ItemsValidator.java | 4 +- .../schema/ItemsValidator202012.java | 6 +- .../java/com/networknt/schema/JsonSchema.java | 5 +- .../schema/PrefixItemsValidator.java | 6 +- .../networknt/schema/PropertiesValidator.java | 30 +- .../walk/AbstractWalkListenerRunner.java | 12 +- .../walk/DefaultItemWalkListenerRunner.java | 20 +- .../DefaultKeywordWalkListenerRunner.java | 18 +- .../DefaultPropertyWalkListenerRunner.java | 19 +- .../com/networknt/schema/walk/WalkEvent.java | 54 ++- .../schema/walk/WalkListenerRunner.java | 12 +- .../com/networknt/schema/Issue724Test.java | 2 +- .../com/networknt/schema/JsonWalkTest.java | 4 +- .../walk/JsonSchemaWalkListenerTest.java | 437 ++++++++++++++++++ 14 files changed, 553 insertions(+), 76 deletions(-) create mode 100644 src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index 1905886f2..90abeb27c 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -248,13 +248,13 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema JsonNodePath instanceLocation, boolean shouldValidateSchema, Set validationMessages) { boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode, instanceLocation, walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory()); + walkSchema.getSchemaNode(), walkSchema, walkSchema.getParentSchema(), this.validationContext); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); } this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode, instanceLocation, this.evaluationPath, walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages); + walkSchema.getSchemaNode(), walkSchema, walkSchema.getParentSchema(), this.validationContext, validationMessages); } diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java index 196d4741b..32bf0e724 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator202012.java +++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java @@ -151,7 +151,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(), walkSchema.getSchemaNode(), - walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory() + walkSchema, walkSchema.getParentSchema(), this.validationContext ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -165,8 +165,8 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema this.evaluationPath, walkSchema.getSchemaLocation(), walkSchema.getSchemaNode(), - walkSchema.getParentSchema(), - this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages + walkSchema, + walkSchema.getParentSchema(), this.validationContext, validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 6c5f1d3c1..82b546b70 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -1043,7 +1043,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode, - this.parentSchema, this.validationContext, this.validationContext.getJsonSchemaFactory())) { + this, this.parentSchema, this.validationContext)) { Set results = null; try { results = v.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); @@ -1059,8 +1059,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode, - this.parentSchema, this.validationContext, this.validationContext.getJsonSchemaFactory(), - errors); + this, this.parentSchema, this.validationContext, errors); } } return errors; diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java index d798503f6..c05cd7b83 100644 --- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java +++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java @@ -137,7 +137,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(), walkSchema.getSchemaNode(), - walkSchema.getParentSchema(), this.validationContext, this.validationContext.getJsonSchemaFactory() + walkSchema, walkSchema.getParentSchema(), this.validationContext ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -151,8 +151,8 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema this.evaluationPath, walkSchema.getSchemaLocation(), walkSchema.getSchemaNode(), - walkSchema.getParentSchema(), - this.validationContext, this.validationContext.getJsonSchemaFactory(), validationMessages + walkSchema, + walkSchema.getParentSchema(), this.validationContext, validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 45e055d85..e2abfe1d5 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -177,7 +177,7 @@ private void applyPropertyDefaults(ObjectNode node) { for (Map.Entry entry : this.schemas.entrySet()) { JsonNode propertyNode = node.get(entry.getKey()); - JsonNode defaultNode = getDefaultNode(entry); + JsonNode defaultNode = getDefaultNode(entry.getValue()); if (defaultNode == null) { continue; } @@ -189,9 +189,27 @@ private void applyPropertyDefaults(ObjectNode node) { } } - private static JsonNode getDefaultNode(final Map.Entry entry) { - JsonSchema propertySchema = entry.getValue(); - return propertySchema.getSchemaNode().get("default"); + private static JsonNode getDefaultNode(JsonSchema propertySchema) { + JsonNode result = propertySchema.getSchemaNode().get("default"); + if (result == null) { + JsonSchemaRef propertySchemaRef = null; + for (JsonValidator validator : propertySchema.getValidators()) { + if (validator instanceof RefValidator) { + propertySchemaRef = ((RefValidator) validator).getSchemaRef(); + break; + } else if (validator instanceof DynamicRefValidator) { + propertySchemaRef = ((DynamicRefValidator) validator).getSchemaRef(); + break; + } else if (validator instanceof RecursiveRefValidator) { + propertySchemaRef = ((RecursiveRefValidator) validator).getSchemaRef(); + break; + } + } + if (propertySchemaRef != null) { + result = getDefaultNode(propertySchemaRef.getSchema()); + } + } + return result; } private void walkSchema(ExecutionContext executionContext, Map.Entry entry, JsonNode node, @@ -203,14 +221,14 @@ private void walkSchema(ExecutionContext executionContext, Map.Entry walkListeners, WalkEvent walkEvent) { diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java index 429771c04..584f7fda6 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java @@ -4,7 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SchemaLocation; import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; @@ -21,22 +20,21 @@ public DefaultItemWalkListenerRunner(List itemWalkListen } @Override - public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, - evaluationPath, schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + evaluationPath, schemaLocation, schemaNode, schema, parentSchema, validationContext); return runPreWalkListeners(itemWalkListeners, walkEvent); } @Override - public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory currentJsonSchemaFactory, Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, - evaluationPath, schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, + ValidationContext validationContext, Set validationMessages) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, + evaluationPath, schemaLocation, schemaNode, schema, parentSchema, validationContext); runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java index 04b1106a5..c60650a3d 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java @@ -16,13 +16,13 @@ public DefaultKeywordWalkListenerRunner(Map } @Override - public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, - JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory) { + JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { boolean continueRunningListenersAndWalk = true; - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, - schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, + schemaLocation, schemaNode, schema, parentSchema, validationContext); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent); @@ -36,13 +36,13 @@ public boolean runPreWalkListeners(ExecutionContext executionContext, String key } @Override - public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, - JsonSchema parentSchema, - ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, Set validationMessages) { - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, - schemaLocation, schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + JsonSchema schema, + JsonSchema parentSchema, ValidationContext validationContext, Set validationMessages) { + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, + schemaLocation, schemaNode, schema, parentSchema, validationContext); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages); diff --git a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java index a8e937d22..b555832d8 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java @@ -4,7 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SchemaLocation; import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; @@ -21,20 +20,20 @@ public DefaultPropertyWalkListenerRunner(List propertyWa } @Override - public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, - JsonSchema parentSchema, ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, schemaLocation, - schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, schemaLocation, + schemaNode, schema, parentSchema, validationContext); return runPreWalkListeners(propertyWalkListeners, walkEvent); } @Override - public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, - JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext, JsonSchemaFactory currentJsonSchemaFactory, Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, node, rootNode, instanceLocation, evaluationPath, schemaLocation, - schemaNode, parentSchema, validationContext, currentJsonSchemaFactory); + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, + JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, JsonSchema schema, + JsonSchema parentSchema, ValidationContext validationContext, Set validationMessages) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, schemaLocation, + schemaNode, schema, parentSchema, validationContext); runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index bf7cf6ca6..f0f6242f1 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -18,12 +18,12 @@ public class WalkEvent { private SchemaLocation schemaLocation; private JsonNodePath evaluationPath; private JsonNode schemaNode; + private JsonSchema schema; private JsonSchema parentSchema; private String keyword; - private JsonNode node; + private JsonNode instanceNode; private JsonNode rootNode; private JsonNodePath instanceLocation; - private JsonSchemaFactory currentJsonSchemaFactory; private ValidationContext validationContext; public ExecutionContext getExecutionContext() { @@ -42,6 +42,15 @@ public JsonNode getSchemaNode() { return schemaNode; } + /** + * Gets the JsonSchema indicated by the schema node. + * + * @return the schema + */ + public JsonSchema getSchema() { + return schema; + } + public JsonSchema getParentSchema() { return parentSchema; } @@ -50,8 +59,13 @@ public String getKeyword() { return keyword; } + public JsonNode getInstanceNode() { + return instanceNode; + } + + @Deprecated public JsonNode getNode() { - return node; + return getInstanceNode(); } public JsonNode getRootNode() { @@ -62,22 +76,28 @@ public JsonNodePath getInstanceLocation() { return instanceLocation; } - public JsonSchema getRefSchema(SchemaLocation schemaUri) { - return currentJsonSchemaFactory.getSchema(schemaUri, validationContext.getConfig()); + public JsonSchema getRefSchema(SchemaLocation schemaLocation) { + return this.validationContext.getJsonSchemaFactory().getSchema(schemaLocation, validationContext.getConfig()); } - public JsonSchema getRefSchema(SchemaLocation schemaUri, SchemaValidatorsConfig schemaValidatorsConfig) { + public JsonSchema getRefSchema(SchemaLocation schemaLocation, SchemaValidatorsConfig schemaValidatorsConfig) { if (schemaValidatorsConfig != null) { - return currentJsonSchemaFactory.getSchema(schemaUri, schemaValidatorsConfig); + return this.validationContext.getJsonSchemaFactory().getSchema(schemaLocation, schemaValidatorsConfig); } else { - return getRefSchema(schemaUri); + return getRefSchema(schemaLocation); } } + public JsonSchemaFactory getJsonSchemaFactory() { + return this.validationContext.getJsonSchemaFactory(); + } + + @Deprecated public JsonSchemaFactory getCurrentJsonSchemaFactory() { - return currentJsonSchemaFactory; + return getJsonSchemaFactory(); } + @Override public String toString() { return "WalkEvent [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation @@ -112,6 +132,11 @@ public WalkEventBuilder schemaNode(JsonNode schemaNode) { return this; } + public WalkEventBuilder schema(JsonSchema schema) { + walkEvent.schema = schema; + return this; + } + public WalkEventBuilder parentSchema(JsonSchema parentSchema) { walkEvent.parentSchema = parentSchema; return this; @@ -122,11 +147,16 @@ public WalkEventBuilder keyword(String keyword) { return this; } - public WalkEventBuilder node(JsonNode node) { - walkEvent.node = node; + public WalkEventBuilder instanceNode(JsonNode node) { + walkEvent.instanceNode = node; return this; } + @Deprecated + public WalkEventBuilder node(JsonNode node) { + return instanceNode(node); + } + public WalkEventBuilder rootNode(JsonNode rootNode) { walkEvent.rootNode = rootNode; return this; @@ -137,8 +167,8 @@ public WalkEventBuilder instanceLocation(JsonNodePath instanceLocation) { return this; } + @Deprecated public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSchemaFactory) { - walkEvent.currentJsonSchemaFactory = currentJsonSchemaFactory; return this; } diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java index 4bba2c63c..33e1ace58 100644 --- a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java @@ -4,7 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SchemaLocation; import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; @@ -13,14 +12,13 @@ public interface WalkListenerRunner { - public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, + public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory jsonSchemaFactory); + JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext); - public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode node, + public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, - JsonSchemaFactory jsonSchemaFactory, Set validationMessages); + JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, + ValidationContext validationContext, Set validationMessages); } diff --git a/src/test/java/com/networknt/schema/Issue724Test.java b/src/test/java/com/networknt/schema/Issue724Test.java index 03f0e3a1a..ef24e14f8 100644 --- a/src/test/java/com/networknt/schema/Issue724Test.java +++ b/src/test/java/com/networknt/schema/Issue724Test.java @@ -72,7 +72,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { .orElse(false); if (isString) { - this.strings.add(walkEvent.getNode().asText()); + this.strings.add(walkEvent.getInstanceNode().asText()); } return WalkFlow.CONTINUE; diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 492bb5a60..3c5c621d7 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -170,7 +170,7 @@ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) { ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); objectNode.put(keywordWalkEvent.getSchemaNode().get("title").textValue().toUpperCase(), - keywordWalkEvent.getNode().textValue()); + keywordWalkEvent.getInstanceNode().textValue()); } return WalkFlow.CONTINUE; } @@ -192,7 +192,7 @@ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { } ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); objectNode.set(keywordWalkEvent.getSchemaNode().get("title").textValue().toLowerCase(), - keywordWalkEvent.getNode()); + keywordWalkEvent.getInstanceNode()); return WalkFlow.SKIP; } diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java new file mode 100644 index 000000000..0061be2f1 --- /dev/null +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.walk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.ApplyDefaultsStrategy; +import com.networknt.schema.InputFormat; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.PathType; +import com.networknt.schema.SchemaId; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersion.VersionFlag; +import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.ValidationMessage; +import com.networknt.schema.ValidationResult; +import com.networknt.schema.ValidatorTypeCode; + +/** + * JsonSchemaWalkListenerTest. + */ +class JsonSchemaWalkListenerTest { + + @Test + void keywordListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + List propertyKeywords = new ArrayList<>(); + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setPathType(PathType.JSON_POINTER); + config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + propertyKeywords.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); + assertTrue(result.getValidationMessages().isEmpty()); + assertEquals(3, propertyKeywords.size()); + assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); + assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath().toString()); + assertEquals("/tags/0", propertyKeywords.get(1).getInstanceLocation().toString()); + assertEquals("image", propertyKeywords.get(1).getInstanceNode().get("name").asText()); + assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getEvaluationPath().toString()); + assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString()); + assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getEvaluationPath().toString()); + assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText()); + } + + @Test + void propertyListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + List properties = new ArrayList<>(); + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setPathType(PathType.JSON_POINTER); + config.addPropertyWalkListener(new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + properties.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); + assertTrue(result.getValidationMessages().isEmpty()); + assertEquals(5, properties.size()); + assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags", properties.get(0).getEvaluationPath().toString()); + + assertEquals("/tags/0/name", properties.get(1).getInstanceLocation().toString()); + assertEquals("image", properties.get(1).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(1).getEvaluationPath().toString()); + + assertEquals("/tags/0/description", properties.get(2).getInstanceLocation().toString()); + assertEquals("An image", properties.get(2).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(2).getEvaluationPath().toString()); + + assertEquals("/tags/1/name", properties.get(3).getInstanceLocation().toString()); + assertEquals("link", properties.get(3).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(3).getEvaluationPath().toString()); + + assertEquals("/tags/1/description", properties.get(4).getInstanceLocation().toString()); + assertEquals("A link", properties.get(4).getInstanceNode().asText()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getEvaluationPath().toString()); + } + + @Test + void draft201909() { + List propertyKeywords = new ArrayList<>(); + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setPathType(PathType.JSON_POINTER); + config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + propertyKeywords.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V201909) + .getSchema(SchemaLocation.of(SchemaId.V201909), config); + String inputData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"kebab-case\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"snake_case\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"a\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); + assertTrue(result.getValidationMessages().isEmpty()); + + assertEquals(28, propertyKeywords.size()); + + assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); + assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(1).getInstanceLocation().toString()); + assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(1).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(2).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(2).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(3).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(3).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(4).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(4).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(5).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(5).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(6).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(6).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(7).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(7).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(8).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(8).getSchemaLocation().toString()); + + assertEquals("/properties/kebab-case", propertyKeywords.get(9).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(9).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(10).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(10).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(11).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(11).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(12).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(12).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(13).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(13).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(14).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(14).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(15).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(15).getSchemaLocation().toString()); + + assertEquals("/properties/snake_case", propertyKeywords.get(16).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(16).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(17).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(17).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(18).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(18).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(19).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(19).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(20).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(20).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(21).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(21).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(22).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(22).getSchemaLocation().toString()); + + assertEquals("/properties/a", propertyKeywords.get(23).getInstanceLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(23).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(24).getInstanceLocation().toString()); + assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(24).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(25).getInstanceLocation().toString()); + assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(25).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(26).getInstanceLocation().toString()); + assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(26).getSchemaLocation().toString()); + + assertEquals("", propertyKeywords.get(27).getInstanceLocation().toString()); + assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getEvaluationPath().toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchemaLocation().toString()); + } + + @Test + void applyDefaults() throws JsonMappingException, JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"S\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + ValidationResult result = schema.walk(inputNode, true); + assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getValidationMessages().isEmpty()); + } + + @Test + void missingRequired() throws JsonMappingException, JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + JsonNode requiredNode = walkEvent.getSchemaNode().get("required"); + List requiredProperties = new ArrayList<>(); + if (requiredNode != null) { + if (requiredNode.isArray()) { + for (JsonNode fieldName : requiredNode) { + requiredProperties.add(fieldName.asText()); + } + } + } + for (String requiredProperty : requiredProperties) { + JsonNode propertyNode = walkEvent.getInstanceNode().get(requiredProperty); + if (propertyNode == null) { + // Get the schema + JsonSchema schema = walkEvent.getSchema(); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + ValidationResult result = schema.walk(inputNode, true); + assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getValidationMessages().isEmpty()); + } +} From 0d93c40fb9ed1cf8be3c9cd9d376e8a831bebc23 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:10:40 +0800 Subject: [PATCH 02/13] Refactor --- .../com/networknt/schema/ItemsValidator.java | 20 ++- .../schema/ItemsValidator202012.java | 24 ++- .../java/com/networknt/schema/JsonSchema.java | 36 +--- .../schema/PrefixItemsValidator.java | 24 ++- .../networknt/schema/PropertiesValidator.java | 50 +++--- .../com/networknt/schema/utils/JsonNodes.java | 80 +++++++++ .../schema/utils/JsonSchemaRefs.java | 49 ++++++ .../walk/AbstractWalkListenerRunner.java | 10 +- .../walk/DefaultItemWalkListenerRunner.java | 13 +- .../DefaultKeywordWalkListenerRunner.java | 15 +- .../DefaultPropertyWalkListenerRunner.java | 14 +- .../com/networknt/schema/walk/WalkEvent.java | 46 +++-- .../schema/walk/WalkListenerRunner.java | 9 +- .../com/networknt/schema/Issue451Test.java | 2 +- .../com/networknt/schema/Issue467Test.java | 6 +- .../com/networknt/schema/Issue724Test.java | 2 +- .../com/networknt/schema/JsonWalkTest.java | 8 +- .../walk/JsonSchemaWalkListenerTest.java | 157 ++++++++++-------- 18 files changed, 340 insertions(+), 225 deletions(-) create mode 100644 src/main/java/com/networknt/schema/utils/JsonNodes.java create mode 100644 src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index 90abeb27c..d885577d3 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.networknt.schema.annotation.JsonNodeAnnotation; +import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.utils.SetView; import org.slf4j.Logger; @@ -205,7 +206,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n JsonNode defaultNode = null; if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults() && this.schema != null) { - defaultNode = this.schema.getSchemaNode().get("default"); + defaultNode = getDefaultNode(this.schema); } int i = 0; for (JsonNode n : arrayNode) { @@ -222,6 +223,17 @@ public Set walk(ExecutionContext executionContext, JsonNode n return validationMessages; } + private static JsonNode getDefaultNode(JsonSchema schema) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema()); + } + } + return result; + } + private void doWalk(ExecutionContext executionContext, HashSet validationMessages, int i, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) { if (this.schema != null) { @@ -247,14 +259,12 @@ private void doWalk(ExecutionContext executionContext, HashSet validationMessages) { boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), - node, rootNode, instanceLocation, walkSchema.getEvaluationPath(), walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema, walkSchema.getParentSchema(), this.validationContext); + node, rootNode, instanceLocation, walkSchema); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); } this.validationContext.getConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), node, rootNode, - instanceLocation, this.evaluationPath, walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema, walkSchema.getParentSchema(), this.validationContext, validationMessages); + instanceLocation, walkSchema, validationMessages); } diff --git a/src/main/java/com/networknt/schema/ItemsValidator202012.java b/src/main/java/com/networknt/schema/ItemsValidator202012.java index 32bf0e724..7315b363e 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator202012.java +++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.networknt.schema.annotation.JsonNodeAnnotation; +import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.utils.SetView; import org.slf4j.Logger; @@ -119,7 +120,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n JsonNode defaultNode = null; if (this.validationContext.getConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults() && this.schema != null) { - defaultNode = this.schema.getSchemaNode().get("default"); + defaultNode = getDefaultNode(this.schema); } for (int i = this.prefixCount; i < node.size(); ++i) { JsonNode n = node.get(i); @@ -139,6 +140,17 @@ public Set walk(ExecutionContext executionContext, JsonNode n return validationMessages; } + private static JsonNode getDefaultNode(JsonSchema schema) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema()); + } + } + return result; + } + private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema, Set validationMessages) { //@formatter:off @@ -148,10 +160,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - walkSchema.getEvaluationPath(), - walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), - walkSchema, walkSchema.getParentSchema(), this.validationContext + walkSchema ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -162,11 +171,8 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - this.evaluationPath, - walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema, - walkSchema.getParentSchema(), this.validationContext, validationMessages + validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 82b546b70..434f814fe 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -22,11 +22,11 @@ import com.networknt.schema.SpecVersion.VersionFlag; import com.networknt.schema.serialization.JsonMapperFactory; import com.networknt.schema.serialization.YamlMapperFactory; +import com.networknt.schema.utils.JsonNodes; import com.networknt.schema.utils.SetView; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -211,7 +211,7 @@ public JsonSchema withConfig(SchemaValidatorsConfig config) { return this; } - ValidationContext getValidationContext() { + public ValidationContext getValidationContext() { return this.validationContext; } @@ -282,7 +282,7 @@ public JsonSchema getSubSchema(JsonNodePath fragment) { SchemaLocation schemaLocation = document.getSchemaLocation(); JsonNodePath evaluationPath = document.getEvaluationPath(); int nameCount = fragment.getNameCount(); - for (int x = 0; x < fragment.getNameCount(); x++) { + for (int x = 0; x < nameCount; x++) { /* * The sub schema is created by iterating through the parents in order to * maintain the lexical parent schema context. @@ -348,29 +348,9 @@ public JsonSchema getSubSchema(JsonNodePath fragment) { protected JsonNode getNode(Object propertyOrIndex) { return getNode(this.schemaNode, propertyOrIndex); } - + protected JsonNode getNode(JsonNode node, Object propertyOrIndex) { - JsonNode value = null; - if (propertyOrIndex instanceof Number) { - value = node.get(((Number) propertyOrIndex).intValue()); - } else { - // In the case of string this represents an escaped json pointer and thus does not reflect the property directly - String unescaped = propertyOrIndex.toString(); - if (unescaped.contains("~")) { - unescaped = unescaped.replace("~1", "/"); - unescaped = unescaped.replace("~0", "~"); - } - if (unescaped.contains("%")) { - try { - unescaped = URLDecoder.decode(unescaped, StandardCharsets.UTF_8.toString()); - } catch (UnsupportedEncodingException e) { - // Do nothing - } - } - - value = node.get(unescaped); - } - return value; + return JsonNodes.get(node, propertyOrIndex); } public JsonSchema findLexicalRoot() { @@ -1042,8 +1022,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n // returns SKIP, then skip the walk. if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode, - this, this.parentSchema, this.validationContext)) { + this)) { Set results = null; try { results = v.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); @@ -1058,8 +1037,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n // Call all the post-walk listeners. this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - v.getEvaluationPath(), v.getSchemaLocation(), this.schemaNode, - this, this.parentSchema, this.validationContext, errors); + this, errors); } } return errors; diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java index c05cd7b83..43085a0b9 100644 --- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java +++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.networknt.schema.annotation.JsonNodeAnnotation; +import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.utils.SetView; import org.slf4j.Logger; @@ -107,7 +108,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n int count = Math.min(node.size(), this.tupleSchema.size()); for (int i = 0; i < count; ++i) { JsonNode n = node.get(i); - JsonNode defaultNode = this.tupleSchema.get(i).getSchemaNode().get("default"); + JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i)); if (n.isNull() && defaultNode != null) { array.set(i, defaultNode); n = defaultNode; @@ -119,6 +120,17 @@ public Set walk(ExecutionContext executionContext, JsonNode n return validationMessages; } + private static JsonNode getDefaultNode(JsonSchema schema) { + JsonNode result = schema.getSchemaNode().get("default"); + if (result == null) { + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema()); + } + } + return result; + } + private void doWalk(ExecutionContext executionContext, Set validationMessages, int i, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) { walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i), @@ -134,10 +146,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - walkSchema.getEvaluationPath(), - walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), - walkSchema, walkSchema.getParentSchema(), this.validationContext + walkSchema ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -148,11 +157,8 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - this.evaluationPath, - walkSchema.getSchemaLocation(), - walkSchema.getSchemaNode(), walkSchema, - walkSchema.getParentSchema(), this.validationContext, validationMessages + validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index e2abfe1d5..b89f2ab6f 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.annotation.JsonNodeAnnotation; +import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.utils.SetView; import com.networknt.schema.walk.WalkListenerRunner; import org.slf4j.Logger; @@ -98,7 +99,7 @@ public Set validate(ExecutionContext executionContext, JsonNo if (errors == null) { errors = new SetView<>(); } - walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner()); + walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), false); } // reset the complex flag to the original value before the recursive call @@ -108,6 +109,18 @@ public Set validate(ExecutionContext executionContext, JsonNo state.setMatchedNode(true); } } else { + if (state.isWalkEnabled()) { + // This tries to make the walk listener consistent between when validation is + // enabled or disabled as when validation is disabled it will walk where node is + // null. + // The actual walk needs to be skipped as the validators assume that node is not + // null. + if (errors == null) { + errors = new SetView<>(); + } + walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner(), true); + } + // check whether the node which has not matched was mandatory or not // the node was mandatory, decide which behavior to employ when validator has @@ -156,7 +169,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n } else { WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner(); for (Map.Entry entry : this.schemas.entrySet()) { - walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner); + walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, validationMessages, propertyWalkListenerRunner, false); } } return validationMessages; @@ -189,24 +202,12 @@ private void applyPropertyDefaults(ObjectNode node) { } } - private static JsonNode getDefaultNode(JsonSchema propertySchema) { - JsonNode result = propertySchema.getSchemaNode().get("default"); + private static JsonNode getDefaultNode(JsonSchema schema) { + JsonNode result = schema.getSchemaNode().get("default"); if (result == null) { - JsonSchemaRef propertySchemaRef = null; - for (JsonValidator validator : propertySchema.getValidators()) { - if (validator instanceof RefValidator) { - propertySchemaRef = ((RefValidator) validator).getSchemaRef(); - break; - } else if (validator instanceof DynamicRefValidator) { - propertySchemaRef = ((DynamicRefValidator) validator).getSchemaRef(); - break; - } else if (validator instanceof RecursiveRefValidator) { - propertySchemaRef = ((RecursiveRefValidator) validator).getSchemaRef(); - break; - } - } - if (propertySchemaRef != null) { - result = getDefaultNode(propertySchemaRef.getSchema()); + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + result = getDefaultNode(schemaRef.getSchema()); } } return result; @@ -214,21 +215,20 @@ private static JsonNode getDefaultNode(JsonSchema propertySchema) { private void walkSchema(ExecutionContext executionContext, Map.Entry entry, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema, - SetView validationMessages, WalkListenerRunner propertyWalkListenerRunner) { + SetView validationMessages, WalkListenerRunner propertyWalkListenerRunner, boolean skipWalk) { JsonSchema propertySchema = entry.getValue(); JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); JsonNodePath path = instanceLocation.append(entry.getKey()); boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(executionContext, ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, rootNode, path, - propertySchema.getEvaluationPath(), propertySchema.getSchemaLocation(), propertySchema.getSchemaNode(), - propertySchema, propertySchema.getParentSchema(), this.validationContext); - if (executeWalk) { + propertySchema); + if (executeWalk && !skipWalk) { validationMessages.union( propertySchema.walk(executionContext, propertyNode, rootNode, path, shouldValidateSchema)); } propertyWalkListenerRunner.runPostWalkListeners(executionContext, ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, - rootNode, path, propertySchema.getEvaluationPath(), - propertySchema.getSchemaLocation(), propertySchema.getSchemaNode(), propertySchema, propertySchema.getParentSchema(), this.validationContext, validationMessages); + rootNode, path, propertySchema, + validationMessages); } diff --git a/src/main/java/com/networknt/schema/utils/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java new file mode 100644 index 000000000..5f29cd500 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.JsonNodePath; + +/** + * Utility methods for JsonNode. + */ +public class JsonNodes { + /** + * Gets the node found at the path. + * + * @param node the node + * @param path the path + * @return the node found at the path or null + */ + public static JsonNode get(JsonNode node, JsonNodePath path) { + int nameCount = path.getNameCount(); + JsonNode current = node; + for (int x = 0; x < nameCount; x++) { + Object segment = path.getElement(x); + JsonNode result = get(current, segment); + if (result == null) { + return null; + } + current = result; + } + return current; + } + + /** + * Gets the node given the property or index. + * + * @param node the node + * @param propertyOrIndex the property or index + * @return the node given the property or index + */ + public static JsonNode get(JsonNode node, Object propertyOrIndex) { + JsonNode value = null; + if (propertyOrIndex instanceof Number) { + value = node.get(((Number) propertyOrIndex).intValue()); + } else { + // In the case of string this represents an escaped json pointer and thus does not reflect the property directly + String unescaped = propertyOrIndex.toString(); + if (unescaped.contains("~")) { + unescaped = unescaped.replace("~1", "/"); + unescaped = unescaped.replace("~0", "~"); + } + if (unescaped.contains("%")) { + try { + unescaped = URLDecoder.decode(unescaped, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + // Do nothing + } + } + + value = node.get(unescaped); + } + return value; + } +} diff --git a/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java new file mode 100644 index 000000000..9a6231fd4 --- /dev/null +++ b/src/main/java/com/networknt/schema/utils/JsonSchemaRefs.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.networknt.schema.utils; + +import com.networknt.schema.DynamicRefValidator; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaRef; +import com.networknt.schema.JsonValidator; +import com.networknt.schema.RecursiveRefValidator; +import com.networknt.schema.RefValidator; + +/** + * Utility methods for JsonSchemaRef. + */ +public class JsonSchemaRefs { + + /** + * Gets the ref. + * + * @param schema the schema + * @return the ref + */ + public static JsonSchemaRef from(JsonSchema schema) { + for (JsonValidator validator : schema.getValidators()) { + if (validator instanceof RefValidator) { + return ((RefValidator) validator).getSchemaRef(); + } else if (validator instanceof DynamicRefValidator) { + return ((DynamicRefValidator) validator).getSchemaRef(); + } else if (validator instanceof RecursiveRefValidator) { + return ((RecursiveRefValidator) validator).getSchemaRef(); + } + } + return null; + } + +} diff --git a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java index b42d9fcb4..ac61d3652 100644 --- a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java @@ -4,8 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.SchemaLocation; -import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -14,12 +12,10 @@ public abstract class AbstractWalkListenerRunner implements WalkListenerRunner { protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema) { return WalkEvent.builder().executionContext(executionContext).instanceLocation(instanceLocation) - .evaluationPath(evaluationPath).keyword(keyword).instanceNode(instanceNode).parentSchema(parentSchema) - .rootNode(rootNode).schemaNode(schemaNode).schema(schema).schemaLocation(schemaLocation) - .validationContext(validationContext).build(); + .keyword(keyword).instanceNode(instanceNode) + .rootNode(rootNode).schema(schema).build(); } protected boolean runPreWalkListeners(List walkListeners, WalkEvent walkEvent) { diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java index 584f7fda6..ccc315b3f 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java @@ -4,8 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.SchemaLocation; -import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -21,20 +19,17 @@ public DefaultItemWalkListenerRunner(List itemWalkListen @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema) { WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, - evaluationPath, schemaLocation, schemaNode, schema, parentSchema, validationContext); + schema); return runPreWalkListeners(itemWalkListeners, walkEvent); } @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, - ValidationContext validationContext, Set validationMessages) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, Set validationMessages) { WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, - evaluationPath, schemaLocation, schemaNode, schema, parentSchema, validationContext); + schema); runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java index c60650a3d..b31e5e8eb 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java @@ -17,12 +17,9 @@ public DefaultKeywordWalkListenerRunner(Map @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, - JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, - JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { + JsonNodePath instanceLocation, JsonSchema schema) { boolean continueRunningListenersAndWalk = true; - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, - schemaLocation, schemaNode, schema, parentSchema, validationContext); + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent); @@ -37,12 +34,8 @@ public boolean runPreWalkListeners(ExecutionContext executionContext, String key @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, - JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, - JsonSchema schema, - JsonSchema parentSchema, ValidationContext validationContext, Set validationMessages) { - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, - schemaLocation, schemaNode, schema, parentSchema, validationContext); + JsonSchema schema, Set validationMessages) { + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages); diff --git a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java index b555832d8..dff8eb581 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java @@ -4,8 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.SchemaLocation; -import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -21,19 +19,15 @@ public DefaultPropertyWalkListenerRunner(List propertyWa @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, - JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, - JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, schemaLocation, - schemaNode, schema, parentSchema, validationContext); + JsonNodePath instanceLocation, JsonSchema schema) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); return runPreWalkListeners(propertyWalkListeners, walkEvent); } @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, - JsonNodePath evaluationPath, SchemaLocation schemaLocation, JsonNode schemaNode, JsonSchema schema, - JsonSchema parentSchema, ValidationContext validationContext, Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, evaluationPath, schemaLocation, - schemaNode, schema, parentSchema, validationContext); + JsonSchema schema, Set validationMessages) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index f0f6242f1..fcbc0b136 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -15,31 +15,29 @@ public class WalkEvent { private ExecutionContext executionContext; - private SchemaLocation schemaLocation; - private JsonNodePath evaluationPath; - private JsonNode schemaNode; private JsonSchema schema; - private JsonSchema parentSchema; private String keyword; private JsonNode instanceNode; private JsonNode rootNode; private JsonNodePath instanceLocation; - private ValidationContext validationContext; public ExecutionContext getExecutionContext() { return executionContext; } + @Deprecated public SchemaLocation getSchemaLocation() { - return schemaLocation; + return getSchema().getSchemaLocation(); } - + + @Deprecated public JsonNodePath getEvaluationPath() { - return evaluationPath; + return getSchema().getEvaluationPath(); } + @Deprecated public JsonNode getSchemaNode() { - return schemaNode; + return getSchema().getSchemaNode(); } /** @@ -51,8 +49,9 @@ public JsonSchema getSchema() { return schema; } + @Deprecated public JsonSchema getParentSchema() { - return parentSchema; + return getSchema().getParentSchema(); } public String getKeyword() { @@ -76,32 +75,29 @@ public JsonNodePath getInstanceLocation() { return instanceLocation; } + @Deprecated public JsonSchema getRefSchema(SchemaLocation schemaLocation) { - return this.validationContext.getJsonSchemaFactory().getSchema(schemaLocation, validationContext.getConfig()); + return getCurrentJsonSchemaFactory().getSchema(schemaLocation, getSchema().getValidationContext().getConfig()); } + @Deprecated public JsonSchema getRefSchema(SchemaLocation schemaLocation, SchemaValidatorsConfig schemaValidatorsConfig) { if (schemaValidatorsConfig != null) { - return this.validationContext.getJsonSchemaFactory().getSchema(schemaLocation, schemaValidatorsConfig); + return getCurrentJsonSchemaFactory().getSchema(schemaLocation, schemaValidatorsConfig); } else { return getRefSchema(schemaLocation); } } - public JsonSchemaFactory getJsonSchemaFactory() { - return this.validationContext.getJsonSchemaFactory(); - } - @Deprecated public JsonSchemaFactory getCurrentJsonSchemaFactory() { - return getJsonSchemaFactory(); + return getSchema().getValidationContext().getJsonSchemaFactory(); } - @Override public String toString() { - return "WalkEvent [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation - + ", instanceLocation=" + instanceLocation + "]"; + return "WalkEvent [evaluationPath=" + getSchema().getEvaluationPath() + ", schemaLocation=" + + getSchema().getSchemaLocation() + ", instanceLocation=" + instanceLocation + "]"; } static class WalkEventBuilder { @@ -117,18 +113,18 @@ public WalkEventBuilder executionContext(ExecutionContext executionContext) { return this; } + @Deprecated public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) { - walkEvent.evaluationPath = evaluationPath; return this; } + @Deprecated public WalkEventBuilder schemaLocation(SchemaLocation schemaLocation) { - walkEvent.schemaLocation = schemaLocation; return this; } + @Deprecated public WalkEventBuilder schemaNode(JsonNode schemaNode) { - walkEvent.schemaNode = schemaNode; return this; } @@ -137,8 +133,8 @@ public WalkEventBuilder schema(JsonSchema schema) { return this; } + @Deprecated public WalkEventBuilder parentSchema(JsonSchema parentSchema) { - walkEvent.parentSchema = parentSchema; return this; } @@ -172,8 +168,8 @@ public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSc return this; } + @Deprecated public WalkEventBuilder validationContext(ValidationContext validationContext) { - walkEvent.validationContext = validationContext; return this; } diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java index 33e1ace58..3e0ec1004 100644 --- a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java @@ -4,8 +4,6 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.SchemaLocation; -import com.networknt.schema.ValidationContext; import com.networknt.schema.ValidationMessage; import java.util.Set; @@ -13,12 +11,9 @@ public interface WalkListenerRunner { public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, ValidationContext validationContext); + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema); public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonNodePath evaluationPath, SchemaLocation schemaLocation, - JsonNode schemaNode, JsonSchema schema, JsonSchema parentSchema, - ValidationContext validationContext, Set validationMessages); + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, Set validationMessages); } diff --git a/src/test/java/com/networknt/schema/Issue451Test.java b/src/test/java/com/networknt/schema/Issue451Test.java index e01e430d9..53a829e50 100644 --- a/src/test/java/com/networknt/schema/Issue451Test.java +++ b/src/test/java/com/networknt/schema/Issue451Test.java @@ -70,7 +70,7 @@ private void walk(JsonNode data, boolean shouldValidate) { private static class CountingWalker implements JsonSchemaWalkListener { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - SchemaLocation path = walkEvent.getSchemaLocation(); + SchemaLocation path = walkEvent.getSchema().getSchemaLocation(); collector(walkEvent.getExecutionContext()).compute(path.toString(), (k, v) -> v == null ? 1 : v + 1); return WalkFlow.CONTINUE; } diff --git a/src/test/java/com/networknt/schema/Issue467Test.java b/src/test/java/com/networknt/schema/Issue467Test.java index 45a52db1a..ad8bb14fe 100644 --- a/src/test/java/com/networknt/schema/Issue467Test.java +++ b/src/test/java/com/networknt/schema/Issue467Test.java @@ -48,7 +48,7 @@ public void shouldWalkKeywordWithValidation() throws URISyntaxException, IOExcep config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - properties.add(walkEvent.getEvaluationPath()); + properties.add(walkEvent.getSchema().getEvaluationPath().append(walkEvent.getKeyword())); return WalkFlow.CONTINUE; } @@ -72,7 +72,7 @@ public void shouldWalkPropertiesWithValidation() throws URISyntaxException, IOEx config.addPropertyWalkListener(new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - properties.add(walkEvent.getEvaluationPath()); + properties.add(walkEvent.getSchema().getEvaluationPath()); return WalkFlow.CONTINUE; } @@ -84,7 +84,7 @@ public void onWalkEnd(WalkEvent walkEvent, Set set) { JsonNode data = mapper.readTree(Issue467Test.class.getResource("/data/issue467.json")); ValidationResult result = schema.walk(data, true); assertEquals( - new HashSet<>(Arrays.asList("$.properties.tags", "$.properties.tags.items[0].properties.category")), + new HashSet<>(Arrays.asList("$.properties.tags", "$.properties.tags.items[0].properties.category", "$.properties.tags.items[0].properties.value")), properties.stream().map(Object::toString).collect(Collectors.toSet())); assertEquals(1, result.getValidationMessages().size()); } diff --git a/src/test/java/com/networknt/schema/Issue724Test.java b/src/test/java/com/networknt/schema/Issue724Test.java index ef24e14f8..47b060cfe 100644 --- a/src/test/java/com/networknt/schema/Issue724Test.java +++ b/src/test/java/com/networknt/schema/Issue724Test.java @@ -65,7 +65,7 @@ static class StringCollector implements JsonSchemaWalkListener { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { boolean isString = - Optional.of(walkEvent.getSchemaNode()) + Optional.of(walkEvent.getSchema().getSchemaNode()) .map(jsonNode -> jsonNode.get("type")) .map(JsonNode::asText) .map(type -> type.equals("string")) diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 3c5c621d7..3ff08e11d 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -162,14 +162,14 @@ private static class AllKeywordListener implements JsonSchemaWalkListener { public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { ObjectMapper mapper = new ObjectMapper(); String keyWordName = keywordWalkEvent.getKeyword(); - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); CollectorContext collectorContext = keywordWalkEvent.getExecutionContext().getCollectorContext(); if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) { ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); - objectNode.put(keywordWalkEvent.getSchemaNode().get("title").textValue().toUpperCase(), + objectNode.put(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toUpperCase(), keywordWalkEvent.getInstanceNode().textValue()); } return WalkFlow.CONTINUE; @@ -191,7 +191,7 @@ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); - objectNode.set(keywordWalkEvent.getSchemaNode().get("title").textValue().toLowerCase(), + objectNode.set(keywordWalkEvent.getSchema().getSchemaNode().get("title").textValue().toLowerCase(), keywordWalkEvent.getInstanceNode()); return WalkFlow.SKIP; } @@ -206,7 +206,7 @@ private static class PropertiesKeywordListener implements JsonSchemaWalkListener @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); if (schemaNode.get("title").textValue().equals("Property3")) { return WalkFlow.SKIP; } diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index 0061be2f1..46efcd653 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -16,10 +16,13 @@ package com.networknt.schema.walk; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; @@ -31,12 +34,15 @@ import com.networknt.schema.InputFormat; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.JsonSchemaRef; import com.networknt.schema.PathType; +import com.networknt.schema.PropertiesValidator; import com.networknt.schema.SchemaId; import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.schema.SpecVersion.VersionFlag; import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.ValidationMessage; import com.networknt.schema.ValidationResult; import com.networknt.schema.ValidatorTypeCode; @@ -106,12 +112,15 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertTrue(result.getValidationMessages().isEmpty()); assertEquals(3, propertyKeywords.size()); assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); - assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath().toString()); + assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath() + .append(propertyKeywords.get(0).getKeyword()).toString()); assertEquals("/tags/0", propertyKeywords.get(1).getInstanceLocation().toString()); assertEquals("image", propertyKeywords.get(1).getInstanceNode().get("name").asText()); - assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath() + .append(propertyKeywords.get(1).getKeyword()).toString()); assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString()); - assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getSchema().getEvaluationPath() + .append(propertyKeywords.get(2).getKeyword()).toString()); assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText()); } @@ -175,23 +184,23 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertTrue(result.getValidationMessages().isEmpty()); assertEquals(5, properties.size()); assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); - assertEquals("/properties/tags", properties.get(0).getEvaluationPath().toString()); + assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().toString()); assertEquals("/tags/0/name", properties.get(1).getInstanceLocation().toString()); assertEquals("image", properties.get(1).getInstanceNode().asText()); - assertEquals("/properties/tags/items/$ref/properties/name", properties.get(1).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(1).getSchema().getEvaluationPath().toString()); assertEquals("/tags/0/description", properties.get(2).getInstanceLocation().toString()); assertEquals("An image", properties.get(2).getInstanceNode().asText()); - assertEquals("/properties/tags/items/$ref/properties/description", properties.get(2).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(2).getSchema().getEvaluationPath().toString()); assertEquals("/tags/1/name", properties.get(3).getInstanceLocation().toString()); assertEquals("link", properties.get(3).getInstanceNode().asText()); - assertEquals("/properties/tags/items/$ref/properties/name", properties.get(3).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties/name", properties.get(3).getSchema().getEvaluationPath().toString()); assertEquals("/tags/1/description", properties.get(4).getInstanceLocation().toString()); assertEquals("A link", properties.get(4).getInstanceNode().asText()); - assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getSchema().getEvaluationPath().toString()); } @Test @@ -234,116 +243,116 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals(28, propertyKeywords.size()); assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); - assertEquals("/properties", propertyKeywords.get(0).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchemaLocation().toString()); + assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath().append(propertyKeywords.get(0).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchema().getSchemaLocation().append(propertyKeywords.get(0).getKeyword()).toString()); assertEquals("", propertyKeywords.get(1).getInstanceLocation().toString()); - assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(1).getSchemaLocation().toString()); + assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath().append(propertyKeywords.get(1).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(1).getSchema().getSchemaLocation().append(propertyKeywords.get(1).getKeyword()).toString()); assertEquals("", propertyKeywords.get(2).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(2).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).getSchema().getEvaluationPath().append(propertyKeywords.get(2).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(2).getSchema().getSchemaLocation().append(propertyKeywords.get(2).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(3).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(3).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).getSchema().getEvaluationPath().append(propertyKeywords.get(3).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(3).getSchema().getSchemaLocation().append(propertyKeywords.get(3).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(4).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(4).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).getSchema().getEvaluationPath().append(propertyKeywords.get(4).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(4).getSchema().getSchemaLocation().append(propertyKeywords.get(4).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(5).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(5).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).getSchema().getEvaluationPath().append(propertyKeywords.get(5).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(5).getSchema().getSchemaLocation().append(propertyKeywords.get(5).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(6).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(6).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).getSchema().getEvaluationPath().append(propertyKeywords.get(6).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(6).getSchema().getSchemaLocation().append(propertyKeywords.get(6).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(7).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(7).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).getSchema().getEvaluationPath().append(propertyKeywords.get(7).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(7).getSchema().getSchemaLocation().append(propertyKeywords.get(7).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(8).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(8).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).getSchema().getEvaluationPath().append(propertyKeywords.get(8).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(8).getSchema().getSchemaLocation().append(propertyKeywords.get(8).getKeyword()).toString()); assertEquals("/properties/kebab-case", propertyKeywords.get(9).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(9).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).getSchema().getEvaluationPath().append(propertyKeywords.get(9).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(9).getSchema().getSchemaLocation().append(propertyKeywords.get(9).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(10).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(10).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).getSchema().getEvaluationPath().append(propertyKeywords.get(10).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(10).getSchema().getSchemaLocation().append(propertyKeywords.get(10).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(11).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(11).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).getSchema().getEvaluationPath().append(propertyKeywords.get(11).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(11).getSchema().getSchemaLocation().append(propertyKeywords.get(11).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(12).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(12).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).getSchema().getEvaluationPath().append(propertyKeywords.get(12).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(12).getSchema().getSchemaLocation().append(propertyKeywords.get(12).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(13).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(13).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).getSchema().getEvaluationPath().append(propertyKeywords.get(13).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(13).getSchema().getSchemaLocation().append(propertyKeywords.get(13).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(14).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(14).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).getSchema().getEvaluationPath().append(propertyKeywords.get(14).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(14).getSchema().getSchemaLocation().append(propertyKeywords.get(14).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(15).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(15).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).getSchema().getEvaluationPath().append(propertyKeywords.get(15).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(15).getSchema().getSchemaLocation().append(propertyKeywords.get(15).getKeyword()).toString()); assertEquals("/properties/snake_case", propertyKeywords.get(16).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(16).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).getSchema().getEvaluationPath().append(propertyKeywords.get(16).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(16).getSchema().getSchemaLocation().append(propertyKeywords.get(16).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(17).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(17).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).getSchema().getEvaluationPath().append(propertyKeywords.get(17).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(17).getSchema().getSchemaLocation().append(propertyKeywords.get(17).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(18).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(18).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).getSchema().getEvaluationPath().append(propertyKeywords.get(18).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/core#/properties", propertyKeywords.get(18).getSchema().getSchemaLocation().append(propertyKeywords.get(18).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(19).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(19).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).getSchema().getEvaluationPath().append(propertyKeywords.get(19).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/applicator#/properties", propertyKeywords.get(19).getSchema().getSchemaLocation().append(propertyKeywords.get(19).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(20).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(20).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).getSchema().getEvaluationPath().append(propertyKeywords.get(20).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(20).getSchema().getSchemaLocation().append(propertyKeywords.get(20).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(21).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(21).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).getSchema().getEvaluationPath().append(propertyKeywords.get(21).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(21).getSchema().getSchemaLocation().append(propertyKeywords.get(21).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(22).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(22).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).getSchema().getEvaluationPath().append(propertyKeywords.get(22).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(22).getSchema().getSchemaLocation().append(propertyKeywords.get(22).getKeyword()).toString()); assertEquals("/properties/a", propertyKeywords.get(23).getInstanceLocation().toString()); - assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(23).getSchemaLocation().toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).getSchema().getEvaluationPath().append(propertyKeywords.get(23).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(23).getSchema().getSchemaLocation().append(propertyKeywords.get(23).getKeyword()).toString()); assertEquals("", propertyKeywords.get(24).getInstanceLocation().toString()); - assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(24).getSchemaLocation().toString()); + assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).getSchema().getEvaluationPath().append(propertyKeywords.get(24).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/validation#/properties", propertyKeywords.get(24).getSchema().getSchemaLocation().append(propertyKeywords.get(24).getKeyword()).toString()); assertEquals("", propertyKeywords.get(25).getInstanceLocation().toString()); - assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(25).getSchemaLocation().toString()); + assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).getSchema().getEvaluationPath().append(propertyKeywords.get(25).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/meta-data#/properties", propertyKeywords.get(25).getSchema().getSchemaLocation().append(propertyKeywords.get(25).getKeyword()).toString()); assertEquals("", propertyKeywords.get(26).getInstanceLocation().toString()); - assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(26).getSchemaLocation().toString()); + assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).getSchema().getEvaluationPath().append(propertyKeywords.get(26).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/format#/properties", propertyKeywords.get(26).getSchema().getSchemaLocation().append(propertyKeywords.get(26).getKeyword()).toString()); assertEquals("", propertyKeywords.get(27).getInstanceLocation().toString()); - assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getEvaluationPath().toString()); - assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchemaLocation().toString()); + assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getSchema().getEvaluationPath().append(propertyKeywords.get(27).getKeyword()).toString()); + assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchema().getSchemaLocation().append(propertyKeywords.get(27).getKeyword()).toString()); } @Test @@ -386,7 +395,7 @@ void missingRequired() throws JsonMappingException, JsonProcessingException { + " \"type\": \"object\",\r\n" + " \"properties\": {\r\n" + " \"s\": {\r\n" - + " \"type\": \"string\"\r\n" + + " \"type\": \"integer\"\r\n" + " },\r\n" + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + " },\r\n" @@ -398,13 +407,13 @@ void missingRequired() throws JsonMappingException, JsonProcessingException { + " }\r\n" + " }\r\n" + "}"; - + Map missingSchemaNode = new LinkedHashMap<>(); SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - JsonNode requiredNode = walkEvent.getSchemaNode().get("required"); + JsonNode requiredNode = walkEvent.getSchema().getSchemaNode().get("required"); List requiredProperties = new ArrayList<>(); if (requiredNode != null) { if (requiredNode.isArray()) { @@ -417,7 +426,14 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { JsonNode propertyNode = walkEvent.getInstanceNode().get(requiredProperty); if (propertyNode == null) { // Get the schema - JsonSchema schema = walkEvent.getSchema(); + PropertiesValidator propertiesValidator = (PropertiesValidator) walkEvent.getSchema().getValidators().stream() + .filter(v -> walkEvent.getKeyword().equals(v.getKeyword())).findFirst().get(); + JsonSchema propertySchema = propertiesValidator.getSchemas().get(requiredProperty); + JsonSchemaRef schemaRef = JsonSchemaRefs.from(propertySchema); + if (schemaRef != null) { + propertySchema = schemaRef.getSchema(); + } + missingSchemaNode.put(requiredProperty, propertySchema.getSchemaNode()); } } return WalkFlow.CONTINUE; @@ -431,7 +447,8 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); ValidationResult result = schema.walk(inputNode, true); - assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); - assertTrue(result.getValidationMessages().isEmpty()); + assertFalse(result.getValidationMessages().isEmpty()); + assertEquals("{\"type\":\"integer\"}", missingSchemaNode.get("s").toString()); + assertEquals("{\"type\":\"string\"}", missingSchemaNode.get("ref").toString()); } } From f72ecb3616a22d9ffd0829eebfceebd55002430a Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:12:48 +0800 Subject: [PATCH 03/13] Refactor --- .../com/networknt/schema/walk/WalkEvent.java | 129 +++++++++++------- 1 file changed, 81 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index fcbc0b136..6926b458d 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -21,27 +21,22 @@ public class WalkEvent { private JsonNode rootNode; private JsonNodePath instanceLocation; + /** + * Gets the execution context. + *

+ * As the listeners should be state-less, this allows listeners to store data in + * the collector context. + * + * @return the execution context + */ public ExecutionContext getExecutionContext() { return executionContext; } - @Deprecated - public SchemaLocation getSchemaLocation() { - return getSchema().getSchemaLocation(); - } - - @Deprecated - public JsonNodePath getEvaluationPath() { - return getSchema().getEvaluationPath(); - } - - @Deprecated - public JsonNode getSchemaNode() { - return getSchema().getSchemaNode(); - } - /** - * Gets the JsonSchema indicated by the schema node. + * Gets the schema that will be used to evaluate the instance node. + *

+ * For the keyword listener, this will allow getting the validator for the given keyword. * * @return the schema */ @@ -49,30 +44,68 @@ public JsonSchema getSchema() { return schema; } - @Deprecated - public JsonSchema getParentSchema() { - return getSchema().getParentSchema(); - } - + /** + * Gets the keyword. + * + * @return the keyword + */ public String getKeyword() { return keyword; } + /** + * Gets the instance node. + * + * @return the instance node + */ public JsonNode getInstanceNode() { return instanceNode; } + /** + * Gets the root instance node. + *

+ * This makes it possible to get the parent node, for instance by getting the + * instance location parent and using the root node. + * + * @return the root node + */ + public JsonNode getRootNode() { + return rootNode; + } + + /** + * Gets the instance location of the instance node. + * + * @return the instance location of the instance node + */ + public JsonNodePath getInstanceLocation() { + return instanceLocation; + } + @Deprecated public JsonNode getNode() { return getInstanceNode(); } + + @Deprecated + public JsonSchema getParentSchema() { + return getSchema().getParentSchema(); + } - public JsonNode getRootNode() { - return rootNode; + @Deprecated + public SchemaLocation getSchemaLocation() { + return getSchema().getSchemaLocation(); } - public JsonNodePath getInstanceLocation() { - return instanceLocation; + @Deprecated + public JsonNodePath getEvaluationPath() { + return getSchema().getEvaluationPath(); + } + + @Deprecated + public JsonNode getSchemaNode() { + return getSchema().getSchemaNode(); } @Deprecated @@ -113,56 +146,56 @@ public WalkEventBuilder executionContext(ExecutionContext executionContext) { return this; } - @Deprecated - public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) { + public WalkEventBuilder schema(JsonSchema schema) { + walkEvent.schema = schema; return this; } - @Deprecated - public WalkEventBuilder schemaLocation(SchemaLocation schemaLocation) { + public WalkEventBuilder keyword(String keyword) { + walkEvent.keyword = keyword; return this; } - @Deprecated - public WalkEventBuilder schemaNode(JsonNode schemaNode) { + public WalkEventBuilder instanceNode(JsonNode node) { + walkEvent.instanceNode = node; return this; } - public WalkEventBuilder schema(JsonSchema schema) { - walkEvent.schema = schema; + public WalkEventBuilder rootNode(JsonNode rootNode) { + walkEvent.rootNode = rootNode; return this; } - @Deprecated - public WalkEventBuilder parentSchema(JsonSchema parentSchema) { + public WalkEventBuilder instanceLocation(JsonNodePath instanceLocation) { + walkEvent.instanceLocation = instanceLocation; return this; } - public WalkEventBuilder keyword(String keyword) { - walkEvent.keyword = keyword; + @Deprecated + public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) { return this; } - public WalkEventBuilder instanceNode(JsonNode node) { - walkEvent.instanceNode = node; + @Deprecated + public WalkEventBuilder schemaLocation(SchemaLocation schemaLocation) { return this; } @Deprecated - public WalkEventBuilder node(JsonNode node) { - return instanceNode(node); - } - - public WalkEventBuilder rootNode(JsonNode rootNode) { - walkEvent.rootNode = rootNode; + public WalkEventBuilder schemaNode(JsonNode schemaNode) { return this; } - public WalkEventBuilder instanceLocation(JsonNodePath instanceLocation) { - walkEvent.instanceLocation = instanceLocation; + @Deprecated + public WalkEventBuilder parentSchema(JsonSchema parentSchema) { return this; } - + + @Deprecated + public WalkEventBuilder node(JsonNode node) { + return instanceNode(node); + } + @Deprecated public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSchemaFactory) { return this; From 98694fafd6f500e84d7ddd38c29f950e20e368d3 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:37:20 +0800 Subject: [PATCH 04/13] Refactor --- .../networknt/schema/PropertiesValidator.java | 6 +- .../com/networknt/schema/walk/WalkEvent.java | 20 +-- .../walk/JsonSchemaWalkListenerTest.java | 116 ++++++++++++++++++ 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index b89f2ab6f..f9e70255a 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -222,7 +222,11 @@ private void walkSchema(ExecutionContext executionContext, Map.Entry @@ -74,6 +65,15 @@ public JsonNode getRootNode() { return rootNode; } + /** + * Gets the instance node. + * + * @return the instance node + */ + public JsonNode getInstanceNode() { + return instanceNode; + } + /** * Gets the instance location of the instance node. * diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index 46efcd653..d744df709 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.ApplyDefaultsStrategy; import com.networknt.schema.InputFormat; import com.networknt.schema.JsonSchema; @@ -42,6 +43,7 @@ import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.schema.SpecVersion.VersionFlag; import com.networknt.schema.serialization.JsonMapperFactory; +import com.networknt.schema.utils.JsonNodes; import com.networknt.schema.utils.JsonSchemaRefs; import com.networknt.schema.ValidationMessage; import com.networknt.schema.ValidationResult; @@ -386,6 +388,120 @@ void applyDefaults() throws JsonMappingException, JsonProcessingException { assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); assertTrue(result.getValidationMessages().isEmpty()); } + + @Test + void applyDefaultsWithWalker() throws JsonMappingException, JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"S\"\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.addPropertyWalkListener(new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + JsonSchema schema = walkEvent.getSchema(); + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + JsonNode defaultNode = schema.getSchemaNode().get("default"); + if (defaultNode != null) { + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + ValidationResult result = schema.walk(inputNode, true); + assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getValidationMessages().isEmpty()); + } + + @Test + void applyInvalidDefaultsWithWalker() throws JsonMappingException, JsonProcessingException { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"title\": \"\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"s\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": 1\r\n" + + " },\r\n" + + " \"ref\": { \"$ref\": \"#/$defs/r\" }\r\n" + + " },\r\n" + + " \"required\": [ \"s\", \"ref\" ],\r\n" + + "\r\n" + + " \"$defs\": {\r\n" + + " \"r\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"default\": \"REF\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.addPropertyWalkListener(new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + JsonSchema schema = walkEvent.getSchema(); + JsonSchemaRef schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + JsonNode defaultNode = schema.getSchemaNode().get("default"); + if (defaultNode != null) { + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), defaultNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + ValidationResult result = schema.walk(inputNode, true); + assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString()); + assertFalse(result.getValidationMessages().isEmpty()); + } @Test void missingRequired() throws JsonMappingException, JsonProcessingException { From a3bbf79c2a7d46c8faa199e4df2c5ae8495b35ef Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:57:40 +0800 Subject: [PATCH 05/13] Refactor --- .../walk/JsonSchemaWalkListenerTest.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index d744df709..aee628dfb 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -82,13 +82,15 @@ void keywordListener() { + " }\r\n" + "}"; - List propertyKeywords = new ArrayList<>(); SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List propertyKeywords = (List) walkEvent.getExecutionContext().getCollectorContext() + .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>()); propertyKeywords.add(walkEvent); return WalkFlow.CONTINUE; } @@ -112,6 +114,8 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess + "}"; ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); assertTrue(result.getValidationMessages().isEmpty()); + @SuppressWarnings("unchecked") + List propertyKeywords = (List) result.getExecutionContext().getCollectorContext().get("propertyKeywords"); assertEquals(3, propertyKeywords.size()); assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath() @@ -125,7 +129,7 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess .append(propertyKeywords.get(2).getKeyword()).toString()); assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText()); } - + @Test void propertyListener() { String schemaData = "{\r\n" @@ -154,13 +158,15 @@ void propertyListener() { + " }\r\n" + "}"; - List properties = new ArrayList<>(); SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addPropertyWalkListener(new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List properties = (List) walkEvent.getExecutionContext().getCollectorContext() + .getCollectorMap().computeIfAbsent("properties", key -> new ArrayList<>()); properties.add(walkEvent); return WalkFlow.CONTINUE; } @@ -184,6 +190,9 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess + "}"; ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); assertTrue(result.getValidationMessages().isEmpty()); + + @SuppressWarnings("unchecked") + List properties = (List) result.getExecutionContext().getCollectorContext().get("properties"); assertEquals(5, properties.size()); assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().toString()); @@ -207,13 +216,15 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess @Test void draft201909() { - List propertyKeywords = new ArrayList<>(); SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List propertyKeywords = (List) walkEvent.getExecutionContext().getCollectorContext() + .getCollectorMap().computeIfAbsent("propertyKeywords", key -> new ArrayList<>()); propertyKeywords.add(walkEvent); return WalkFlow.CONTINUE; } @@ -241,9 +252,12 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess + "}"; ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); assertTrue(result.getValidationMessages().isEmpty()); + + @SuppressWarnings("unchecked") + List propertyKeywords = (List) result.getExecutionContext().getCollectorContext().getCollectorMap().get("propertyKeywords"); assertEquals(28, propertyKeywords.size()); - + assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath().append(propertyKeywords.get(0).getKeyword()).toString()); assertEquals("https://json-schema.org/draft/2019-09/schema#/properties", propertyKeywords.get(0).getSchema().getSchemaLocation().append(propertyKeywords.get(0).getKeyword()).toString()); @@ -356,7 +370,7 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).getSchema().getEvaluationPath().append(propertyKeywords.get(27).getKeyword()).toString()); assertEquals("https://json-schema.org/draft/2019-09/meta/content#/properties", propertyKeywords.get(27).getSchema().getSchemaLocation().append(propertyKeywords.get(27).getKeyword()).toString()); } - + @Test void applyDefaults() throws JsonMappingException, JsonProcessingException { String schemaData = "{\r\n" @@ -388,7 +402,7 @@ void applyDefaults() throws JsonMappingException, JsonProcessingException { assertEquals("{\"s\":\"S\",\"ref\":\"REF\"}", inputNode.toString()); assertTrue(result.getValidationMessages().isEmpty()); } - + @Test void applyDefaultsWithWalker() throws JsonMappingException, JsonProcessingException { String schemaData = "{\r\n" @@ -501,6 +515,11 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess ValidationResult result = schema.walk(inputNode, true); assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString()); assertFalse(result.getValidationMessages().isEmpty()); + + inputNode = JsonMapperFactory.getInstance().readTree("{}"); + result = schema.walk(inputNode, false); + assertEquals("{\"s\":1,\"ref\":\"REF\"}", inputNode.toString()); + assertTrue(result.getValidationMessages().isEmpty()); } @Test From 0bbca17f5aeedf0832bd8cb076053e472406b1d0 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:05:38 +0800 Subject: [PATCH 06/13] Refactor --- .../com/networknt/schema/ItemsValidator.java | 4 +- .../schema/ItemsValidator202012.java | 4 +- .../java/com/networknt/schema/JsonSchema.java | 4 +- .../schema/PrefixItemsValidator.java | 4 +- .../networknt/schema/PropertiesValidator.java | 4 +- .../com/networknt/schema/utils/JsonNodes.java | 10 +- .../walk/AbstractWalkListenerRunner.java | 5 +- .../walk/DefaultItemWalkListenerRunner.java | 9 +- .../DefaultKeywordWalkListenerRunner.java | 8 +- .../DefaultPropertyWalkListenerRunner.java | 9 +- .../com/networknt/schema/walk/WalkEvent.java | 17 ++ .../schema/walk/WalkListenerRunner.java | 5 +- .../walk/JsonSchemaWalkListenerTest.java | 153 ++++++++++++++++++ 13 files changed, 206 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index d885577d3..698f8bc65 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -259,12 +259,12 @@ private void doWalk(ExecutionContext executionContext, HashSet validationMessages) { boolean executeWalk = this.validationContext.getConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, ValidatorTypeCode.ITEMS.getValue(), - node, rootNode, instanceLocation, walkSchema); + 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, - instanceLocation, walkSchema, validationMessages); + 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 7315b363e..a9cd02a2f 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator202012.java +++ b/src/main/java/com/networknt/schema/ItemsValidator202012.java @@ -160,7 +160,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - walkSchema + walkSchema, this ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -172,7 +172,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema rootNode, instanceLocation, walkSchema, - validationMessages + this, validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 434f814fe..d397cfaf2 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -1022,7 +1022,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n // returns SKIP, then skip the walk. if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this)) { + this, v)) { Set results = null; try { results = v.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); @@ -1037,7 +1037,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n // Call all the post-walk listeners. this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this, errors); + this, v, errors); } } return errors; diff --git a/src/main/java/com/networknt/schema/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/PrefixItemsValidator.java index 43085a0b9..94bd136b3 100644 --- a/src/main/java/com/networknt/schema/PrefixItemsValidator.java +++ b/src/main/java/com/networknt/schema/PrefixItemsValidator.java @@ -146,7 +146,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema node, rootNode, instanceLocation, - walkSchema + walkSchema, this ); if (executeWalk) { validationMessages.addAll(walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema)); @@ -158,7 +158,7 @@ private void walkSchema(ExecutionContext executionContext, JsonSchema walkSchema rootNode, instanceLocation, walkSchema, - validationMessages + this, validationMessages ); //@formatter:on } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index f9e70255a..a0238c1f7 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -221,7 +221,7 @@ private void walkSchema(ExecutionContext executionContext, Map.Entry T get(JsonNode node, JsonNodePath path) { int nameCount = path.getNameCount(); JsonNode current = node; for (int x = 0; x < nameCount; x++) { @@ -44,7 +45,7 @@ public static JsonNode get(JsonNode node, JsonNodePath path) { } current = result; } - return current; + return (T) current; } /** @@ -54,7 +55,8 @@ public static JsonNode get(JsonNode node, JsonNodePath path) { * @param propertyOrIndex the property or index * @return the node given the property or index */ - public static JsonNode get(JsonNode node, Object propertyOrIndex) { + @SuppressWarnings("unchecked") + public static T get(JsonNode node, Object propertyOrIndex) { JsonNode value = null; if (propertyOrIndex instanceof Number) { value = node.get(((Number) propertyOrIndex).intValue()); @@ -75,6 +77,6 @@ public static JsonNode get(JsonNode node, Object propertyOrIndex) { value = node.get(unescaped); } - return value; + return (T) value; } } diff --git a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java index ac61d3652..92d564c10 100644 --- a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java @@ -4,6 +4,7 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonValidator; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -12,10 +13,10 @@ public abstract class AbstractWalkListenerRunner implements WalkListenerRunner { protected WalkEvent constructWalkEvent(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) { return WalkEvent.builder().executionContext(executionContext).instanceLocation(instanceLocation) .keyword(keyword).instanceNode(instanceNode) - .rootNode(rootNode).schema(schema).build(); + .rootNode(rootNode).schema(schema).validator(validator).build(); } protected boolean runPreWalkListeners(List walkListeners, WalkEvent walkEvent) { diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java index ccc315b3f..eba7f054b 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java @@ -4,6 +4,7 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonValidator; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -19,17 +20,17 @@ public DefaultItemWalkListenerRunner(List itemWalkListen @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) { WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, - schema); + schema, validator); return runPreWalkListeners(itemWalkListeners, walkEvent); } @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, Set validationMessages) { + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set validationMessages) { WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, - schema); + schema, validator); runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java index b31e5e8eb..0435f5323 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultKeywordWalkListenerRunner.java @@ -17,9 +17,9 @@ public DefaultKeywordWalkListenerRunner(Map @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, - JsonNodePath instanceLocation, JsonSchema schema) { + JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) { boolean continueRunningListenersAndWalk = true; - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); continueRunningListenersAndWalk = runPreWalkListeners(currentKeywordListeners, keywordWalkEvent); @@ -34,8 +34,8 @@ public boolean runPreWalkListeners(ExecutionContext executionContext, String key @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, - JsonSchema schema, Set validationMessages) { - WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); + JsonSchema schema, JsonValidator validator, Set validationMessages) { + WalkEvent keywordWalkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator); // Run Listeners that are setup only for this keyword. List currentKeywordListeners = keywordWalkListenersMap.get(keyword); runPostWalkListeners(currentKeywordListeners, keywordWalkEvent, validationMessages); diff --git a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java index dff8eb581..e10253f97 100644 --- a/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/DefaultPropertyWalkListenerRunner.java @@ -4,6 +4,7 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonValidator; import com.networknt.schema.ValidationMessage; import java.util.List; @@ -19,15 +20,15 @@ public DefaultPropertyWalkListenerRunner(List propertyWa @Override public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, - JsonNodePath instanceLocation, JsonSchema schema) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); + JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator); return runPreWalkListeners(propertyWalkListeners, walkEvent); } @Override public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, JsonNode rootNode, JsonNodePath instanceLocation, - JsonSchema schema, Set validationMessages) { - WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema); + JsonSchema schema, JsonValidator validator, Set validationMessages) { + WalkEvent walkEvent = constructWalkEvent(executionContext, keyword, instanceNode, rootNode, instanceLocation, schema, validator); runPostWalkListeners(propertyWalkListeners, walkEvent, validationMessages); } diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index 32c489be2..1b07b5d9b 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -5,6 +5,7 @@ import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.JsonValidator; import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaValidatorsConfig; import com.networknt.schema.ValidationContext; @@ -20,6 +21,7 @@ public class WalkEvent { private JsonNode rootNode; private JsonNode instanceNode; private JsonNodePath instanceLocation; + private JsonValidator validator; /** * Gets the execution context. @@ -83,6 +85,16 @@ public JsonNodePath getInstanceLocation() { return instanceLocation; } + /** + * Gets the validator that corresponds with the keyword. + * + * @return the validator + */ + @SuppressWarnings("unchecked") + public T getValidator() { + return (T) this.validator; + } + @Deprecated public JsonNode getNode() { return getInstanceNode(); @@ -171,6 +183,11 @@ public WalkEventBuilder instanceLocation(JsonNodePath instanceLocation) { return this; } + public WalkEventBuilder validator(JsonValidator validator) { + walkEvent.validator = validator; + return this; + } + @Deprecated public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) { return this; diff --git a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java index 3e0ec1004..0476d8bc2 100644 --- a/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/WalkListenerRunner.java @@ -4,6 +4,7 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonValidator; import com.networknt.schema.ValidationMessage; import java.util.Set; @@ -11,9 +12,9 @@ public interface WalkListenerRunner { public boolean runPreWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema); + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator); public void runPostWalkListeners(ExecutionContext executionContext, String keyword, JsonNode instanceNode, - JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, Set validationMessages); + JsonNode rootNode, JsonNodePath instanceLocation, JsonSchema schema, JsonValidator validator, Set validationMessages); } diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index aee628dfb..6b91bb3b9 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -33,6 +33,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.ApplyDefaultsStrategy; import com.networknt.schema.InputFormat; +import com.networknt.schema.ItemsValidator; +import com.networknt.schema.ItemsValidator202012; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.JsonSchemaRef; @@ -117,6 +119,7 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess @SuppressWarnings("unchecked") List propertyKeywords = (List) result.getExecutionContext().getCollectorContext().get("propertyKeywords"); assertEquals(3, propertyKeywords.size()); + assertEquals("properties", propertyKeywords.get(0).getValidator().getKeyword()); assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath() .append(propertyKeywords.get(0).getKeyword()).toString()); @@ -194,6 +197,8 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess @SuppressWarnings("unchecked") List properties = (List) result.getExecutionContext().getCollectorContext().get("properties"); assertEquals(5, properties.size()); + assertEquals("properties", properties.get(0).getValidator().getKeyword()); + assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().toString()); @@ -214,6 +219,154 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getSchema().getEvaluationPath().toString()); } + @Test + void itemsListener() { + String schemaData = "{\r\n" + + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setPathType(PathType.JSON_POINTER); + config.addItemWalkListener(new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext().getCollectorContext() + .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>()); + items.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); + assertTrue(result.getValidationMessages().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(2, items.size()); + assertEquals("items", items.get(0).getValidator().getKeyword()); + assertTrue(items.get(0).getValidator() instanceof ItemsValidator); + + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); + + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); + } + + @Test + void items202012Listener() { + String schemaData = "{\r\n" + + " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n" + + " \"type\": \"object\",\r\n" + + " \"description\": \"Default Description\",\r\n" + + " \"properties\": {\r\n" + + " \"tags\": {\r\n" + + " \"type\": \"array\",\r\n" + + " \"items\": {\r\n" + + " \"$ref\": \"#/definitions/tag\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"definitions\": {\r\n" + + " \"tag\": {\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"description\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setPathType(PathType.JSON_POINTER); + config.addItemWalkListener(new JsonSchemaWalkListener() { + + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + @SuppressWarnings("unchecked") + List items = (List) walkEvent.getExecutionContext().getCollectorContext() + .getCollectorMap().computeIfAbsent("items", key -> new ArrayList<>()); + items.add(walkEvent); + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(schemaData, config); + String inputData = "{\r\n" + + " \"tags\": [\r\n" + + " {\r\n" + + " \"name\": \"image\",\r\n" + + " \"description\": \"An image\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"name\": \"link\",\r\n" + + " \"description\": \"A link\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + ValidationResult result = schema.walk(inputData, InputFormat.JSON, true); + assertTrue(result.getValidationMessages().isEmpty()); + + @SuppressWarnings("unchecked") + List items = (List) result.getExecutionContext().getCollectorContext().get("items"); + assertEquals(2, items.size()); + assertEquals("items", items.get(0).getValidator().getKeyword()); + assertTrue(items.get(0).getValidator() instanceof ItemsValidator202012); + + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); + + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); + assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); + } + @Test void draft201909() { SchemaValidatorsConfig config = new SchemaValidatorsConfig(); From 08c30a7f90205c6ba7a03f6110e0a8a6c66c0c36 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:10:05 +0800 Subject: [PATCH 07/13] Refactor --- .../com/networknt/schema/walk/JsonSchemaWalkListenerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index 6b91bb3b9..b6b78eb26 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -125,6 +125,8 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess .append(propertyKeywords.get(0).getKeyword()).toString()); assertEquals("/tags/0", propertyKeywords.get(1).getInstanceLocation().toString()); assertEquals("image", propertyKeywords.get(1).getInstanceNode().get("name").asText()); + assertEquals("/properties/tags/items/$ref/properties", + propertyKeywords.get(1).getValidator().getEvaluationPath().toString()); assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath() .append(propertyKeywords.get(1).getKeyword()).toString()); assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString()); From 97c24d5470b7d809d9f633256d832bfc502892ab Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:11:11 +0800 Subject: [PATCH 08/13] Refactor --- .../com/networknt/schema/walk/JsonSchemaWalkListenerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index b6b78eb26..d4d47b4a1 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -716,8 +716,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { JsonNode propertyNode = walkEvent.getInstanceNode().get(requiredProperty); if (propertyNode == null) { // Get the schema - PropertiesValidator propertiesValidator = (PropertiesValidator) walkEvent.getSchema().getValidators().stream() - .filter(v -> walkEvent.getKeyword().equals(v.getKeyword())).findFirst().get(); + PropertiesValidator propertiesValidator = walkEvent.getValidator(); JsonSchema propertySchema = propertiesValidator.getSchemas().get(requiredProperty); JsonSchemaRef schemaRef = JsonSchemaRefs.from(propertySchema); if (schemaRef != null) { From 0c1f6d40baf3dcdede2661959ca528290a6bf3f3 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:09:37 +0800 Subject: [PATCH 09/13] Update docs --- doc/upgrading.md | 26 +++++++++++++++++++++++++- doc/walkers.md | 35 +++++++++++++++-------------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/doc/upgrading.md b/doc/upgrading.md index a72ad228e..909016fe1 100644 --- a/doc/upgrading.md +++ b/doc/upgrading.md @@ -6,7 +6,31 @@ This contains information on the notable or breaking changes in each version. ### 1.4.0 -This contains breaking changes in how custom meta-schemas are created. +This contains breaking changes +- to those using the walk functionality +- in how custom meta-schemas are created + +The following are the breaking changes to those using the walk functionality. + +When using the walker with defaults the `default` across a `$ref` are properly resolved and used. + +The behavior for the property listener is now more consistent whether or not validation is enabled. Previously if validation is enabled but the property is `null` the property listener is not called while if validation is not enabled it will be called. Now the property listener will be called in both scenarios. + + +`WalkEvent` +| Field | Change | Details +|--------------------------|--------------|---------- +| `schemaLocation` | Removed | For keywords: `getValidator().getSchemaLocation()`. For items and properties: `getSchema().getSchemaLocation()` +| `evaluationPath` | Removed | For keywords: `getValidator().getEvaluationPath()`. For items and properties: `getSchema().getEvaluationPath()` +| `schemaNode` | Removed | `getSchema().getSchemaNode()` +| `parentSchema` | Removed | `getSchema().getParentSchema()` +| `schema` | New | For keywords this is the parent schema of the validator. For items and properties this is the item or property schema being evaluated. +| `node` | Renamed | `instanceNode` +| `currentJsonSchemaFactory`| Removed | `getSchema().getValidationContext().getJsonSchemaFactory()` +| `validator` | New | The validator indicated by the keyword. + + +The following are the breaking changes in how custom meta-schemas are created. `JsonSchemaFactory` * The following were renamed on `JsonSchemaFactory` builder diff --git a/doc/walkers.md b/doc/walkers.md index b06fb120f..ff84c2686 100644 --- a/doc/walkers.md +++ b/doc/walkers.md @@ -38,20 +38,16 @@ public interface JsonSchemaWalker { The JSONValidator interface extends this new interface thus allowing all the validator's defined in library to implement this new interface. BaseJsonValidator class provides a default implementation of the walk method. In this case the walk method does nothing but validating based on shouldValidateSchema parameter. ```java -/** + /** * This is default implementation of walk method. Its job is to call the * validate method if shouldValidateSchema is enabled. */ @Override - public Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, - JsonNodePath instanceLocation, boolean shouldValidateSchema); - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages = validate(executionContext, node, rootNode, instanceLocation); - } - return validationMessages; + default Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation, boolean shouldValidateSchema) { + return shouldValidateSchema ? validate(executionContext, node, rootNode, instanceLocation) + : Collections.emptySet(); } - ``` A new walk method added to the JSONSchema class allows us to walk through the JSONSchema. @@ -95,7 +91,7 @@ A new walk method added to the JSONSchema class allows us to walk through the JS ``` Following code snippet shows how to call the walk method on a JsonSchema instance. -``` +```java ValidationResult result = jsonSchema.walk(data,false); ``` @@ -122,7 +118,7 @@ private static class PropertiesKeywordListener implements JsonSchemaWalkListener @Override public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { - JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); + JsonNode schemaNode = keywordWalkEvent.getSchema().getSchemaNode(); if (schemaNode.get("title").textValue().equals("Property3")) { return WalkFlow.SKIP; } @@ -146,7 +142,7 @@ SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new PropertiesKeywordListener()); final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema) .build(); this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); @@ -160,7 +156,7 @@ Both property walk listeners and keyword walk listener can be modeled by using t SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); schemaValidatorsConfig.addPropertyWalkListener(new ExamplePropertyWalkListener()); final JsonSchemaFactory schemaFactory = JsonSchemaFactory - .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).metaSchema(metaSchema) .build(); this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); @@ -176,16 +172,15 @@ Following snippet shows the details captured by WalkEvent instance. ```java public class WalkEvent { - private ExecutionContext executionContext; - private SchemaLocation schemaLocation; - private JsonNodePath evaluationPath; - private JsonNode schemaNode; - private JsonSchema parentSchema; + private JsonSchema schema; private String keyword; - private JsonNode node; private JsonNode rootNode; + private JsonNode instanceNode; private JsonNodePath instanceLocation; + private JsonValidator validator; + ... +} ``` ### Sample Flow @@ -285,7 +280,7 @@ But if we apply defaults while walking, then required validation passes, and the JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4); SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); schemaValidatorsConfig.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)); - JsonSchema jsonSchema = schemaFactory.getSchema(getClass().getClassLoader().getResourceAsStream("schema.json"), schemaValidatorsConfig); + JsonSchema jsonSchema = schemaFactory.getSchema(SchemaLocation.of("classpath:schema.json"), schemaValidatorsConfig); JsonNode inputNode = objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data.json")); ValidationResult result = jsonSchema.walk(inputNode, true); From e2566b4685d1e35f5f5b3b82278fe1b93c7fb6b9 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:11:06 +0800 Subject: [PATCH 10/13] Refactor --- .../com/networknt/schema/walk/WalkEvent.java | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index 1b07b5d9b..f0a207968 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -4,11 +4,7 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.JsonNodePath; import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.JsonValidator; -import com.networknt.schema.SchemaLocation; -import com.networknt.schema.SchemaValidatorsConfig; -import com.networknt.schema.ValidationContext; /** * Encapsulation of Walk data that is passed into the {@link JsonSchemaWalkListener}. @@ -95,50 +91,6 @@ public T getValidator() { return (T) this.validator; } - @Deprecated - public JsonNode getNode() { - return getInstanceNode(); - } - - @Deprecated - public JsonSchema getParentSchema() { - return getSchema().getParentSchema(); - } - - @Deprecated - public SchemaLocation getSchemaLocation() { - return getSchema().getSchemaLocation(); - } - - @Deprecated - public JsonNodePath getEvaluationPath() { - return getSchema().getEvaluationPath(); - } - - @Deprecated - public JsonNode getSchemaNode() { - return getSchema().getSchemaNode(); - } - - @Deprecated - public JsonSchema getRefSchema(SchemaLocation schemaLocation) { - return getCurrentJsonSchemaFactory().getSchema(schemaLocation, getSchema().getValidationContext().getConfig()); - } - - @Deprecated - public JsonSchema getRefSchema(SchemaLocation schemaLocation, SchemaValidatorsConfig schemaValidatorsConfig) { - if (schemaValidatorsConfig != null) { - return getCurrentJsonSchemaFactory().getSchema(schemaLocation, schemaValidatorsConfig); - } else { - return getRefSchema(schemaLocation); - } - } - - @Deprecated - public JsonSchemaFactory getCurrentJsonSchemaFactory() { - return getSchema().getValidationContext().getJsonSchemaFactory(); - } - @Override public String toString() { return "WalkEvent [evaluationPath=" + getSchema().getEvaluationPath() + ", schemaLocation=" @@ -188,41 +140,6 @@ public WalkEventBuilder validator(JsonValidator validator) { return this; } - @Deprecated - public WalkEventBuilder evaluationPath(JsonNodePath evaluationPath) { - return this; - } - - @Deprecated - public WalkEventBuilder schemaLocation(SchemaLocation schemaLocation) { - return this; - } - - @Deprecated - public WalkEventBuilder schemaNode(JsonNode schemaNode) { - return this; - } - - @Deprecated - public WalkEventBuilder parentSchema(JsonSchema parentSchema) { - return this; - } - - @Deprecated - public WalkEventBuilder node(JsonNode node) { - return instanceNode(node); - } - - @Deprecated - public WalkEventBuilder currentJsonSchemaFactory(JsonSchemaFactory currentJsonSchemaFactory) { - return this; - } - - @Deprecated - public WalkEventBuilder validationContext(ValidationContext validationContext) { - return this; - } - public WalkEvent build() { return walkEvent; } @@ -232,5 +149,4 @@ public WalkEvent build() { public static WalkEventBuilder builder() { return new WalkEventBuilder(); } - } From 59ce2e9ab5aa1589ad634d9f2c18ba169137581d Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:22:20 +0800 Subject: [PATCH 11/13] Refactor --- doc/upgrading.md | 5 +- doc/walkers.md | 50 +++++++++---------- .../java/com/networknt/schema/JsonSchema.java | 41 +++++++-------- .../networknt/schema/PropertiesValidator.java | 1 - 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/doc/upgrading.md b/doc/upgrading.md index 909016fe1..506e96761 100644 --- a/doc/upgrading.md +++ b/doc/upgrading.md @@ -10,15 +10,14 @@ This contains breaking changes - to those using the walk functionality - in how custom meta-schemas are created -The following are the breaking changes to those using the walk functionality. - When using the walker with defaults the `default` across a `$ref` are properly resolved and used. The behavior for the property listener is now more consistent whether or not validation is enabled. Previously if validation is enabled but the property is `null` the property listener is not called while if validation is not enabled it will be called. Now the property listener will be called in both scenarios. +The following are the breaking changes to those using the walk functionality. `WalkEvent` -| Field | Change | Details +| Field | Change | Notes |--------------------------|--------------|---------- | `schemaLocation` | Removed | For keywords: `getValidator().getSchemaLocation()`. For items and properties: `getSchema().getSchemaLocation()` | `evaluationPath` | Removed | For keywords: `getValidator().getEvaluationPath()`. For items and properties: `getSchema().getEvaluationPath()` diff --git a/doc/walkers.md b/doc/walkers.md index ff84c2686..51736e499 100644 --- a/doc/walkers.md +++ b/doc/walkers.md @@ -53,46 +53,46 @@ The JSONValidator interface extends this new interface thus allowing all the val A new walk method added to the JSONSchema class allows us to walk through the JSONSchema. ```java - public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { - // Create the collector context object. - CollectorContext collectorContext = new CollectorContext(); - // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); - Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); - // Load all the data from collectors into the context. - collectorContext.loadCollectors(); - // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, collectorContext); - return validationResult; + public ValidationResult walk(JsonNode node, boolean validate) { + return walk(createExecutionContext(), node, validate); } @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); + public Set walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, + JsonNodePath instanceLocation, boolean shouldValidateSchema) { + Set errors = new LinkedHashSet<>(); // Walk through all the JSONWalker's. - for (Entry entry : validators.entrySet()) { - JsonWalker jsonWalker = entry.getValue(); - String schemaPathWithKeyword = entry.getKey(); + for (JsonValidator validator : getValidators()) { + JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath(); try { - // Call all the pre-walk listeners. If all the pre-walk listeners return true - // then continue to walk method. - if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema)) { - validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); + // Call all the pre-walk listeners. If at least one of the pre walk listeners + // returns SKIP, then skip the walk. + if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, + evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, + this, validator)) { + Set results = null; + try { + results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + if (results != null && !results.isEmpty()) { + errors.addAll(results); + } + } } } finally { // Call all the post-walk listeners. - keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema, validationMessages); + this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, + evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, + this, validator, errors); } } - return validationMessages; + return errors; } ``` Following code snippet shows how to call the walk method on a JsonSchema instance. ```java -ValidationResult result = jsonSchema.walk(data,false); +ValidationResult result = jsonSchema.walk(data, false); ``` diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index d397cfaf2..5d42c593d 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -898,22 +898,24 @@ public ValidationResult validateAndCollect(ExecutionContext executionContext, Js * @return ValidationResult */ private ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) { - // Get the config. - SchemaValidatorsConfig config = this.validationContext.getConfig(); - // Get the collector context from the thread local. - CollectorContext collectorContext = executionContext.getCollectorContext(); // Set the walkEnabled and isValidationEnabled flag in internal validator state. setValidatorState(executionContext, false, true); // Validate. Set errors = validate(executionContext, jsonNode, rootNode, instanceLocation); + + // Get the config. + SchemaValidatorsConfig config = this.validationContext.getConfig(); + // When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors. if (config.doLoadCollectors()) { + // Get the collector context. + CollectorContext collectorContext = executionContext.getCollectorContext(); + // Load all the data from collectors into the context. collectorContext.loadCollectors(); } // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, executionContext); - return validationResult; + return new ValidationResult(errors, executionContext); } public ValidationResult validateAndCollect(JsonNode node) { @@ -992,22 +994,22 @@ public ValidationResult walkAtNode(ExecutionContext executionContext, JsonNode n private ValidationResult walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) { - // Get the config. - SchemaValidatorsConfig config = this.validationContext.getConfig(); - // Get the collector context. - CollectorContext collectorContext = executionContext.getCollectorContext(); // Set the walkEnabled flag in internal validator state. setValidatorState(executionContext, true, shouldValidateSchema); // Walk through the schema. Set errors = walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + + // Get the config. + SchemaValidatorsConfig config = this.validationContext.getConfig(); // When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors. if (config.doLoadCollectors()) { + // Get the collector context. + CollectorContext collectorContext = executionContext.getCollectorContext(); + // Load all the data from collectors into the context. collectorContext.loadCollectors(); } - - ValidationResult validationResult = new ValidationResult(errors, executionContext); - return validationResult; + return new ValidationResult(errors, executionContext); } @Override @@ -1015,20 +1017,19 @@ public Set walk(ExecutionContext executionContext, JsonNode n JsonNodePath instanceLocation, boolean shouldValidateSchema) { Set errors = new LinkedHashSet<>(); // Walk through all the JSONWalker's. - for (JsonValidator v : getValidators()) { - JsonNodePath evaluationPathWithKeyword = v.getEvaluationPath(); + for (JsonValidator validator : getValidators()) { + JsonNodePath evaluationPathWithKeyword = validator.getEvaluationPath(); try { // Call all the pre-walk listeners. If at least one of the pre walk listeners // returns SKIP, then skip the walk. if (this.validationContext.getConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this, v)) { + this, validator)) { Set results = null; try { - results = v.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + results = validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); } finally { - if (results == null || results.isEmpty()) { - } else { + if (results != null && !results.isEmpty()) { errors.addAll(results); } } @@ -1037,7 +1038,7 @@ public Set walk(ExecutionContext executionContext, JsonNode n // Call all the post-walk listeners. this.validationContext.getConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this, v, errors); + this, validator, errors); } } return errors; diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index a0238c1f7..a47e61c67 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -233,7 +233,6 @@ private void walkSchema(ExecutionContext executionContext, Map.Entry getSchemas() { From 1c08577b009824a6631c874e9d89fce42de11d0e Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:20:20 +0800 Subject: [PATCH 12/13] Refactor --- src/main/java/com/networknt/schema/ConstValidator.java | 5 ++--- .../java/com/networknt/schema/DynamicRefValidator.java | 2 +- src/main/java/com/networknt/schema/ItemsValidator.java | 7 +++++-- src/main/java/com/networknt/schema/JsonSchema.java | 4 +--- .../com/networknt/schema/MaxPropertiesValidator.java | 4 +++- .../com/networknt/schema/MinPropertiesValidator.java | 4 +++- .../com/networknt/schema/RecursiveRefValidator.java | 2 +- src/main/java/com/networknt/schema/RefValidator.java | 2 +- src/main/java/com/networknt/schema/TypeValidator.java | 10 +++++----- .../com/networknt/schema/UniqueItemsValidator.java | 4 +++- 10 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/networknt/schema/ConstValidator.java b/src/main/java/com/networknt/schema/ConstValidator.java index a06f16cf0..291144b62 100644 --- a/src/main/java/com/networknt/schema/ConstValidator.java +++ b/src/main/java/com/networknt/schema/ConstValidator.java @@ -27,11 +27,10 @@ */ public class ConstValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(ConstValidator.class); - JsonNode schemaNode; - public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { + public ConstValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, + JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.CONST, validationContext); - this.schemaNode = schemaNode; } public Set validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) { diff --git a/src/main/java/com/networknt/schema/DynamicRefValidator.java b/src/main/java/com/networknt/schema/DynamicRefValidator.java index a113d1167..6f72993a4 100644 --- a/src/main/java/com/networknt/schema/DynamicRefValidator.java +++ b/src/main/java/com/networknt/schema/DynamicRefValidator.java @@ -28,7 +28,7 @@ public class DynamicRefValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(DynamicRefValidator.class); - protected JsonSchemaRef schema; + protected final JsonSchemaRef schema; public DynamicRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.DYNAMIC_REF, validationContext); diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index 698f8bc65..7aede162e 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -36,7 +36,7 @@ public class ItemsValidator extends BaseJsonValidator { private final JsonSchema schema; private final List tupleSchema; - private Boolean additionalItems; + private final Boolean additionalItems; private final JsonSchema additionalSchema; private Boolean hasUnevaluatedItemsValidator = null; @@ -48,6 +48,8 @@ public class ItemsValidator extends BaseJsonValidator { public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext); + Boolean additionalItems = null; + this.tupleSchema = new ArrayList<>(); JsonSchema foundSchema = null; JsonSchema foundAdditionalSchema = null; @@ -67,7 +69,7 @@ public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath if (addItemNode != null) { additionalItemsSchemaNode = addItemNode; if (addItemNode.isBoolean()) { - this.additionalItems = addItemNode.asBoolean(); + additionalItems = addItemNode.asBoolean(); } else if (addItemNode.isObject()) { foundAdditionalSchema = validationContext.newSchema( parentSchema.schemaLocation.append(PROPERTY_ADDITIONAL_ITEMS), @@ -75,6 +77,7 @@ public ItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath } } } + this.additionalItems = additionalItems; this.schema = foundSchema; this.additionalSchema = foundAdditionalSchema; this.additionalItemsEvaluationPath = parentSchema.evaluationPath.append(PROPERTY_ADDITIONAL_ITEMS); diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 5d42c593d..74ed724d9 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -543,9 +543,7 @@ public Set validate(ExecutionContext executionContext, JsonNo try { results = v.validate(executionContext, jsonNode, rootNode, instanceLocation); } finally { - if (results == null || results.isEmpty()) { - // Do nothing if valid - } else { + if (results != null && !results.isEmpty()) { if (errors == null) { errors = new SetView<>(); } diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java index f23f303e6..0d33b127b 100644 --- a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java @@ -29,13 +29,15 @@ public class MaxPropertiesValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(MaxPropertiesValidator.class); - private int max; + private final int max; public MaxPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES, validationContext); if (schemaNode.canConvertToExactIntegral()) { max = schemaNode.intValue(); + } else { + max = Integer.MAX_VALUE; } } diff --git a/src/main/java/com/networknt/schema/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java index 26d6de145..1ff56645d 100644 --- a/src/main/java/com/networknt/schema/MinPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/MinPropertiesValidator.java @@ -29,13 +29,15 @@ public class MinPropertiesValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(MinPropertiesValidator.class); - protected int min; + protected final int min; public MinPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES, validationContext); if (schemaNode.canConvertToExactIntegral()) { min = schemaNode.intValue(); + } else { + min = 0; } } diff --git a/src/main/java/com/networknt/schema/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/RecursiveRefValidator.java index 0ca0f26fb..e73bb7d08 100644 --- a/src/main/java/com/networknt/schema/RecursiveRefValidator.java +++ b/src/main/java/com/networknt/schema/RecursiveRefValidator.java @@ -28,7 +28,7 @@ public class RecursiveRefValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(RecursiveRefValidator.class); - protected JsonSchemaRef schema; + protected final JsonSchemaRef schema; public RecursiveRefValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.RECURSIVE_REF, validationContext); diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java index 1959231e3..1a32e6e0e 100644 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ b/src/main/java/com/networknt/schema/RefValidator.java @@ -28,7 +28,7 @@ public class RefValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(RefValidator.class); - protected JsonSchemaRef schema; + protected final JsonSchemaRef schema; private static final String REF_CURRENT = "#"; diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java index 7f7736ec5..245f69073 100644 --- a/src/main/java/com/networknt/schema/TypeValidator.java +++ b/src/main/java/com/networknt/schema/TypeValidator.java @@ -29,16 +29,16 @@ public class TypeValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(TypeValidator.class); - private JsonType schemaType; - private JsonSchema parentSchema; - private UnionTypeValidator unionTypeValidator; + private final JsonType schemaType; + private final UnionTypeValidator unionTypeValidator; public TypeValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext); this.schemaType = TypeFactory.getSchemaNodeType(schemaNode); - this.parentSchema = parentSchema; if (this.schemaType == JsonType.UNION) { this.unionTypeValidator = new UnionTypeValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext); + } else { + this.unionTypeValidator = null; } } @@ -47,7 +47,7 @@ public JsonType getSchemaType() { } public boolean equalsToSchemaType(JsonNode node) { - return JsonNodeUtil.equalsToSchemaType(node,this.schemaType, this.parentSchema, this.validationContext); + return JsonNodeUtil.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.validationContext); } @Override diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java index be43b965b..a061b978e 100644 --- a/src/main/java/com/networknt/schema/UniqueItemsValidator.java +++ b/src/main/java/com/networknt/schema/UniqueItemsValidator.java @@ -30,12 +30,14 @@ public class UniqueItemsValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(UniqueItemsValidator.class); - private boolean unique = false; + private final boolean unique; public UniqueItemsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS, validationContext); if (schemaNode.isBoolean()) { unique = schemaNode.booleanValue(); + } else { + unique = false; } } From 65be26c77231fa3cae811d23618b2764dcf8b9f1 Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:16:48 +0800 Subject: [PATCH 13/13] Add test --- .../walk/JsonSchemaWalkListenerTest.java | 107 +++++++++++++++--- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java index d4d47b4a1..d4b67012c 100644 --- a/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/JsonSchemaWalkListenerTest.java @@ -20,16 +20,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; 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.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.ApplyDefaultsStrategy; import com.networknt.schema.InputFormat; @@ -96,7 +99,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { propertyKeywords.add(walkEvent); return WalkFlow.CONTINUE; } - + @Override public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { } @@ -166,7 +169,7 @@ void propertyListener() { SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addPropertyWalkListener(new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { @SuppressWarnings("unchecked") @@ -200,7 +203,7 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess List properties = (List) result.getExecutionContext().getCollectorContext().get("properties"); assertEquals(5, properties.size()); assertEquals("properties", properties.get(0).getValidator().getKeyword()); - + assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().toString()); @@ -248,11 +251,11 @@ void itemsListener() { + " }\r\n" + " }\r\n" + "}"; - + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addItemWalkListener(new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { @SuppressWarnings("unchecked") @@ -287,10 +290,10 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals(2, items.size()); assertEquals("items", items.get(0).getValidator().getKeyword()); assertTrue(items.get(0).getValidator() instanceof ItemsValidator); - + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); - + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); } @@ -322,11 +325,11 @@ void items202012Listener() { + " }\r\n" + " }\r\n" + "}"; - + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setPathType(PathType.JSON_POINTER); config.addItemWalkListener(new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { @SuppressWarnings("unchecked") @@ -361,10 +364,10 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals(2, items.size()); assertEquals("items", items.get(0).getValidator().getKeyword()); assertTrue(items.get(0).getValidator() instanceof ItemsValidator202012); - + assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); - + assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); } @@ -548,7 +551,7 @@ void applyDefaults() throws JsonMappingException, JsonProcessingException { + " }\r\n" + " }\r\n" + "}"; - + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.setApplyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)); JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); @@ -580,10 +583,10 @@ void applyDefaultsWithWalker() throws JsonMappingException, JsonProcessingExcept + " }\r\n" + " }\r\n" + "}"; - + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.addPropertyWalkListener(new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() @@ -637,10 +640,10 @@ void applyInvalidDefaultsWithWalker() throws JsonMappingException, JsonProcessin + " }\r\n" + " }\r\n" + "}"; - + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.addPropertyWalkListener(new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() @@ -700,7 +703,7 @@ void missingRequired() throws JsonMappingException, JsonProcessingException { Map missingSchemaNode = new LinkedHashMap<>(); SchemaValidatorsConfig config = new SchemaValidatorsConfig(); config.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new JsonSchemaWalkListener() { - + @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { JsonNode requiredNode = walkEvent.getSchema().getSchemaNode().get("required"); @@ -727,7 +730,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { } return WalkFlow.CONTINUE; } - + @Override public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { } @@ -740,4 +743,72 @@ public void onWalkEnd(WalkEvent walkEvent, Set validationMess assertEquals("{\"type\":\"integer\"}", missingSchemaNode.get("s").toString()); assertEquals("{\"type\":\"string\"}", missingSchemaNode.get("ref").toString()); } + + @Test + void generateDataWithWalker() throws JsonMappingException, JsonProcessingException { + Map> generators = new HashMap<>(); + generators.put("name.findName", () -> "John Doe"); + generators.put("internet.email", () -> "john.doe@gmail.com"); + + String schemaData = "{\r\n" + + " \"type\": \"object\",\r\n" + + " \"properties\": {\r\n" + + " \"name\": {\r\n" + + " \"$ref\": \"#/$defs/Name\"\r\n" + + " },\r\n" + + " \"email\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"faker\": \"internet.email\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"name\",\r\n" + + " \"email\"\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"Name\": {\r\n" + + " \"type\": \"string\",\r\n" + + " \"faker\": \"name.findName\"\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.addPropertyWalkListener(new JsonSchemaWalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() + || walkEvent.getInstanceNode().isNull()) { + JsonSchema schema = walkEvent.getSchema(); + JsonSchemaRef schemaRef = null; + do { + schemaRef = JsonSchemaRefs.from(schema); + if (schemaRef != null) { + schema = schemaRef.getSchema(); + } + } while (schemaRef != null); + JsonNode fakerNode = schema.getSchemaNode().get("faker"); + if (fakerNode != null) { + String faker = fakerNode.asText(); + String fakeData = generators.get(faker).get(); + JsonNode fakeDataNode = JsonNodeFactory.instance.textNode(fakeData); + ObjectNode parentNode = (ObjectNode) JsonNodes.get(walkEvent.getRootNode(), + walkEvent.getInstanceLocation().getParent()); + parentNode.set(walkEvent.getInstanceLocation().getName(-1), fakeDataNode); + } + } + return WalkFlow.CONTINUE; + } + + @Override + public void onWalkEnd(WalkEvent walkEvent, Set validationMessages) { + } + }); + + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config); + JsonNode inputNode = JsonMapperFactory.getInstance().readTree("{}"); + ValidationResult result = schema.walk(inputNode, true); + assertEquals("{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"}", inputNode.toString()); + assertTrue(result.getValidationMessages().isEmpty()); + } }