From 1cf0bf83bbfb570b2aa328de4b1aa15d9a5040cb Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:18:17 +0800 Subject: [PATCH] Refactor evaluation context out from validator state --- .../com/networknt/schema/AbsoluteIri.java | 7 +- .../networknt/schema/ExecutionContext.java | 68 +++- .../java/com/networknt/schema/Schema.java | 304 ++++++++---------- .../com/networknt/schema/SchemaContext.java | 9 +- .../com/networknt/schema/SchemaLocation.java | 8 +- .../com/networknt/schema/SchemaRegistry.java | 77 ++--- .../schema/SchemaRegistryConfig.java | 76 +---- .../java/com/networknt/schema/Validator.java | 9 - .../schema/annotation/Annotation.java | 9 + .../com/networknt/schema/dialect/Dialect.java | 8 +- .../schema/format/BaseFormatValidator.java | 5 +- .../keyword/AbstractKeywordValidator.java | 87 ++++- .../AdditionalPropertiesValidator.java | 26 +- .../schema/keyword/AllOfValidator.java | 30 +- .../schema/keyword/AnnotationKeyword.java | 8 +- .../schema/keyword/AnyOfValidator.java | 74 ++--- .../schema/keyword/BaseKeywordValidator.java | 65 +--- .../schema/keyword/ConstValidator.java | 8 +- .../schema/keyword/ContainsValidator.java | 42 +-- .../keyword/ContentEncodingValidator.java | 8 +- .../keyword/ContentMediaTypeValidator.java | 7 +- .../schema/keyword/DependenciesValidator.java | 16 +- .../schema/keyword/DependentRequired.java | 6 +- .../schema/keyword/DependentSchemas.java | 24 +- .../keyword/DiscriminatorValidator.java | 6 +- .../schema/keyword/DynamicRefValidator.java | 84 ++--- .../schema/keyword/EnumValidator.java | 6 +- .../keyword/ExclusiveMaximumValidator.java | 6 +- .../keyword/ExclusiveMinimumValidator.java | 6 +- .../schema/keyword/FalseValidator.java | 11 +- .../schema/keyword/FormatKeyword.java | 5 +- .../schema/keyword/FormatValidator.java | 8 +- .../networknt/schema/keyword/IfValidator.java | 75 ++++- .../schema/keyword/ItemsLegacyValidator.java | 162 ++++++---- .../schema/keyword/ItemsValidator.java | 44 +-- .../com/networknt/schema/keyword/Keyword.java | 4 +- .../networknt/schema/keyword/KeywordType.java | 9 +- .../schema/keyword/MaxItemsValidator.java | 8 +- .../schema/keyword/MaxLengthValidator.java | 6 +- .../keyword/MaxPropertiesValidator.java | 6 +- .../schema/keyword/MaximumValidator.java | 6 +- .../schema/keyword/MinItemsValidator.java | 8 +- .../schema/keyword/MinLengthValidator.java | 6 +- .../keyword/MinMaxContainsValidator.java | 5 +- .../keyword/MinPropertiesValidator.java | 6 +- .../schema/keyword/MinimumValidator.java | 6 +- .../schema/keyword/MultipleOfValidator.java | 6 +- .../schema/keyword/NonValidationKeyword.java | 12 +- .../schema/keyword/NotAllowedValidator.java | 6 +- .../schema/keyword/NotValidator.java | 8 +- .../schema/keyword/OneOfValidator.java | 71 ++-- .../keyword/PatternPropertiesValidator.java | 32 +- .../schema/keyword/PatternValidator.java | 6 +- .../schema/keyword/PrefixItemsValidator.java | 58 ++-- .../schema/keyword/PropertiesValidator.java | 66 ++-- .../PropertyDependenciesValidator.java | 48 ++- .../keyword/PropertyNamesValidator.java | 8 +- .../schema/keyword/ReadOnlyValidator.java | 6 +- .../schema/keyword/RecursiveRefValidator.java | 83 +---- .../schema/keyword/RefValidator.java | 49 +-- .../schema/keyword/RequiredValidator.java | 6 +- .../schema/keyword/TrueValidator.java | 4 +- .../schema/keyword/TypeValidator.java | 14 +- .../keyword/UnevaluatedItemsValidator.java | 16 +- .../UnevaluatedPropertiesValidator.java | 15 +- .../schema/keyword/UnionTypeValidator.java | 21 +- .../schema/keyword/UniqueItemsValidator.java | 6 +- .../schema/keyword/WriteOnlyValidator.java | 6 +- .../schema/output/OutputUnitData.java | 5 +- .../com/networknt/schema/path/NodePath.java | 2 +- .../java/com/networknt/schema/path/Path.java | 23 ++ .../networknt/schema/utils/JsonNodeTypes.java | 20 +- .../networknt/schema/utils/SchemaRefs.java | 7 +- .../com/networknt/schema/utils/Strings.java | 43 +++ .../networknt/schema/utils/TypeFactory.java | 3 +- .../com/networknt/schema/walk/WalkEvent.java | 10 +- .../schema/CollectorContextTest.java | 24 +- .../schema/CustomMetaSchemaTest.java | 8 +- .../schema/FormatKeywordFactoryTest.java | 3 +- .../com/networknt/schema/IfValidatorTest.java | 4 +- .../com/networknt/schema/Issue1091Test.java | 19 +- .../com/networknt/schema/Issue467Test.java | 4 +- .../schema/JsonSchemaPreloadTest.java | 1 - .../com/networknt/schema/JsonWalkTest.java | 8 +- .../com/networknt/schema/MessageTest.java | 10 +- .../PatternPropertiesValidatorTest.java | 10 +- .../schema/PropertiesValidatorTest.java | 26 -- ...ursiveReferenceValidatorExceptionTest.java | 4 +- .../com/networknt/schema/TypeFactoryTest.java | 4 +- .../keyword/PropertiesValidatorTest.java | 123 +++++++ .../PropertyDependenciesValidatorTest.java | 2 +- .../networknt/schema/oas/OpenApi30Test.java | 4 +- .../schema/path/EvaluationPathTest.java | 88 +++++ .../schema/walk/WalkListenerTest.java | 98 +++--- 94 files changed, 1369 insertions(+), 1195 deletions(-) create mode 100644 src/main/java/com/networknt/schema/path/Path.java delete mode 100644 src/test/java/com/networknt/schema/PropertiesValidatorTest.java create mode 100644 src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java create mode 100644 src/test/java/com/networknt/schema/path/EvaluationPathTest.java diff --git a/src/main/java/com/networknt/schema/AbsoluteIri.java b/src/main/java/com/networknt/schema/AbsoluteIri.java index 1ade74363..890f5e003 100644 --- a/src/main/java/com/networknt/schema/AbsoluteIri.java +++ b/src/main/java/com/networknt/schema/AbsoluteIri.java @@ -17,6 +17,8 @@ import java.util.Objects; +import com.networknt.schema.utils.Strings; + /** * The absolute IRI is an IRI without the fragment. *

@@ -131,7 +133,7 @@ public static String resolve(String parent, String iri) { } base = parent(base, scheme); - String[] iriParts = iri.split("/"); + String[] iriParts = Strings.split(iri, '/'); for (int x = 0; x < iriParts.length; x++) { if ("..".equals(iriParts[x])) { base = parent(base, scheme); @@ -149,9 +151,6 @@ public static String resolve(String parent, String iri) { } } } - if (iri.endsWith("/")) { - base = base + "/"; - } return base; } } diff --git a/src/main/java/com/networknt/schema/ExecutionContext.java b/src/main/java/com/networknt/schema/ExecutionContext.java index 5cb78dba1..51037797f 100644 --- a/src/main/java/com/networknt/schema/ExecutionContext.java +++ b/src/main/java/com/networknt/schema/ExecutionContext.java @@ -16,6 +16,7 @@ package com.networknt.schema; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,7 +26,7 @@ import com.networknt.schema.annotation.Annotations; import com.networknt.schema.keyword.DiscriminatorState; import com.networknt.schema.path.NodePath; -import com.networknt.schema.result.InstanceResults; +//import com.networknt.schema.result.InstanceResults; import com.networknt.schema.walk.WalkConfig; /** @@ -37,11 +38,40 @@ public class ExecutionContext { private CollectorContext collectorContext = null; private Annotations annotations = null; - private InstanceResults instanceResults = null; +// private InstanceResults instanceResults = null; private List errors = new ArrayList<>(); - private Map discriminatorMapping = new HashMap<>(); + private final Map discriminatorMapping = new HashMap<>(); + NodePath evaluationPath; + final ArrayDeque evaluationSchema = new ArrayDeque<>(64); + final ArrayDeque evaluationSchemaPath = new ArrayDeque<>(64); + + public NodePath getEvaluationPath() { + return evaluationPath; + } + + public void evaluationPathAddLast(String token) { + this.evaluationPath = evaluationPath.append(token); + } + + public void evaluationPathAddLast(int token) { + this.evaluationPath = evaluationPath.append(token); + } + + public void evaluationPathRemoveLast() { + this.evaluationPath = evaluationPath.getParent(); + } + + + public ArrayDeque getEvaluationSchema() { + return evaluationSchema; + } + + public ArrayDeque getEvaluationSchemaPath() { + return evaluationSchemaPath; + } + public Map getDiscriminatorMapping() { return discriminatorMapping; } @@ -156,12 +186,12 @@ public Annotations getAnnotations() { return annotations; } - public InstanceResults getInstanceResults() { - if (this.instanceResults == null) { - this.instanceResults = new InstanceResults(); - } - return instanceResults; - } +// public InstanceResults getInstanceResults() { +// if (this.instanceResults == null) { +// this.instanceResults = new InstanceResults(); +// } +// return instanceResults; +// } /** * Determines if the validator should immediately throw a fail fast exception if @@ -224,4 +254,24 @@ public void walkConfig(Consumer customizer) { customizer.accept(builder); this.walkConfig = builder.build(); } + + boolean unevaluatedPropertiesPresent = false; + + boolean unevaluatedItemsPresent = false; + + public boolean isUnevaluatedPropertiesPresent() { + return this.unevaluatedPropertiesPresent; + } + + public boolean isUnevaluatedItemsPresent() { + return this.unevaluatedItemsPresent; + } + + public void setUnevaluatedPropertiesPresent(boolean set) { + this.unevaluatedPropertiesPresent = set; + } + + public void setUnevaluatedItemsPresent(boolean set) { + this.unevaluatedItemsPresent = set; + } } diff --git a/src/main/java/com/networknt/schema/Schema.java b/src/main/java/com/networknt/schema/Schema.java index b11dd60ee..bfd264019 100644 --- a/src/main/java/com/networknt/schema/Schema.java +++ b/src/main/java/com/networknt/schema/Schema.java @@ -40,6 +40,7 @@ import com.networknt.schema.resource.ClasspathResourceLoader; import com.networknt.schema.resource.InputStreamSource; import com.networknt.schema.resource.ResourceLoader; +import com.networknt.schema.annotation.Annotation; import com.networknt.schema.keyword.KeywordType; import com.networknt.schema.utils.JsonNodes; @@ -63,9 +64,11 @@ public class Schema implements Validator { * The validators sorted and indexed by evaluation path. */ private List validators = null; + private boolean unevaluatedPropertiesPresent = false; + private boolean unevaluatedItemsPresent = false; + private boolean validatorsLoaded = false; private boolean recursiveAnchor = false; - private TypeValidator typeValidator = null; protected final JsonNode schemaNode; protected final Schema parentSchema; @@ -73,9 +76,6 @@ public class Schema implements Validator { protected final SchemaContext schemaContext; protected final boolean suppressSubSchemaRetrieval; - protected final NodePath evaluationPath; - protected final Schema evaluationParentSchema; - public JsonNode getSchemaNode() { return this.schemaNode; } @@ -92,17 +92,6 @@ public boolean isSuppressSubSchemaRetrieval() { return suppressSubSchemaRetrieval; } - public NodePath getEvaluationPath() { - return evaluationPath; - } - - public Schema getEvaluationParentSchema() { - if (this.evaluationParentSchema != null) { - return this.evaluationParentSchema; - } - return getParentSchema(); - } - protected Schema fetchSubSchemaNode(SchemaContext schemaContext) { return this.suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(this.schemaNode, schemaContext); } @@ -147,6 +136,14 @@ public static NodePath getInstance() { } public void validate(ExecutionContext executionContext, JsonNode node) { + /* Previously the evaluation path started with the fragment of the schema due to the way it was implemented + * as part of the schema's state + * int count = this.schemaLocation.getFragment().getNameCount(); + * for (int x = 0; x < count; x++) { + * executionContext.evaluationPath.addLast(this.schemaLocation.getFragment().getElement(x)); + * } + */ + executionContext.evaluationPath = atRoot(); validate(executionContext, node, node, atRoot()); } @@ -166,8 +163,8 @@ protected NodePath atRoot() { return new NodePath(this.schemaContext.getSchemaRegistryConfig().getPathType()); } - static Schema from(SchemaContext schemaContext, SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parent, boolean suppressSubSchemaRetrieval) { - return new Schema(schemaContext, schemaLocation, evaluationPath, schemaNode, parent, suppressSubSchemaRetrieval); + static Schema from(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parent, boolean suppressSubSchemaRetrieval) { + return new Schema(schemaContext, schemaLocation, schemaNode, parent, suppressSubSchemaRetrieval); } private boolean hasNoFragment(SchemaLocation schemaLocation) { @@ -209,19 +206,13 @@ private static SchemaLocation resolve(SchemaLocation schemaLocation, JsonNode sc } } - private Schema(SchemaContext schemaContext, SchemaLocation schemaLocation, NodePath evaluationPath, + private Schema(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parent, boolean suppressSubSchemaRetrieval) { - /* - super(resolve(schemaLocation, schemaNode, parent == null, schemaContext), evaluationPath, schemaNode, parent, - null, null, schemaContext, suppressSubSchemaRetrieval); - */ this.schemaContext = schemaContext; this.schemaLocation = resolve(schemaLocation, schemaNode, parent == null, schemaContext); this.schemaNode = schemaNode; this.parentSchema = parent; this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; - this.evaluationPath = evaluationPath; - this.evaluationParentSchema = null; String id = this.schemaContext.resolveSchemaId(this.schemaNode); if (id != null) { @@ -271,39 +262,29 @@ private Schema(SchemaContext schemaContext, SchemaLocation schemaLocation, NodeP * @param validators the validators * @param validatorsLoaded whether the validators are preloaded * @param recursiveAnchor whether this is has a recursive anchor - * @param typeValidator the type validator * @param id the id * @param suppressSubSchemaRetrieval to suppress sub schema retrieval * @param schemaNode the schema node * @param schemaContext the schema context * @param parentSchema the parent schema * @param schemaLocation the schema location - * @param evaluationPath the evaluation path - * @param evaluationParentSchema the evaluation parent schema * @param errorMessage the error message */ protected Schema( - /* Below from JsonSchema */ List validators, boolean validatorsLoaded, boolean recursiveAnchor, TypeValidator typeValidator, String id, - /* Below from BaseJsonValidator */ boolean suppressSubSchemaRetrieval, JsonNode schemaNode, SchemaContext schemaContext, Schema parentSchema, SchemaLocation schemaLocation, - NodePath evaluationPath, - Schema evaluationParentSchema, Map errorMessage) { -// super(suppressSubSchemaRetrieval, schemaNode, schemaContext, errorMessageType, keyword, -// parentSchema, schemaLocation, evaluationPath, evaluationParentSchema, errorMessage); this.validators = validators; this.validatorsLoaded = validatorsLoaded; this.recursiveAnchor = recursiveAnchor; - this.typeValidator = typeValidator; this.id = id; this.schemaContext = schemaContext; @@ -311,90 +292,15 @@ protected Schema( this.schemaNode = schemaNode; this.parentSchema = parentSchema; this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval; - this.evaluationPath = evaluationPath; - this.evaluationParentSchema = evaluationParentSchema; } - /** - * Creates a schema using the current one as a template with the parent as the - * ref. - *

- * This is typically used if this schema is a schema resource that can be - * pointed to by various references. - * - * @param refEvaluationParentSchema the parent ref - * @param refEvaluationPath the ref evaluation path - * @return the schema - */ - public Schema fromRef(Schema refEvaluationParentSchema, NodePath refEvaluationPath) { - SchemaContext schemaContext = new SchemaContext(this.getSchemaContext().getDialect(), - this.getSchemaContext().getSchemaRegistry(), - refEvaluationParentSchema.getSchemaContext().getSchemaReferences(), - refEvaluationParentSchema.getSchemaContext().getSchemaResources(), - refEvaluationParentSchema.getSchemaContext().getDynamicAnchors()); - NodePath evaluationPath = refEvaluationPath; - Schema evaluationParentSchema = refEvaluationParentSchema; - // Validator state is reset due to the changes in evaluation path - boolean validatorsLoaded = false; - TypeValidator typeValidator = null; - List validators = null; - - return new Schema( - /* Below from JsonSchema */ - validators, - validatorsLoaded, - recursiveAnchor, - typeValidator, - id, - /* Below from BaseJsonValidator */ - suppressSubSchemaRetrieval, - schemaNode, - schemaContext, - parentSchema, schemaLocation, evaluationPath, - evaluationParentSchema, /* errorMessage */ null); - } - -// public Schema withConfig(SchemaValidatorsConfig config) { -// if (this.getValidationContext().getMetaSchema().getKeywords().containsKey("discriminator") -// && !config.isDiscriminatorKeywordEnabled()) { -// config = SchemaValidatorsConfig.builder(config) -// .discriminatorKeywordEnabled(true) -// .nullableKeywordEnabled(true) -// .build(); -// } -// if (!this.getValidationContext().getSchemaRegistryConfig().equals(config)) { -// ValidationContext schemaContext = new ValidationContext(this.getValidationContext().getMetaSchema(), -// this.getValidationContext().getSchemaRegistry(), config, -// this.getValidationContext().getSchemaReferences(), -// this.getValidationContext().getSchemaResources(), -// this.getValidationContext().getDynamicAnchors()); -// boolean validatorsLoaded = false; -// TypeValidator typeValidator = null; -// List validators = null; -// return new Schema( -// /* Below from JsonSchema */ -// validators, -// validatorsLoaded, -// recursiveAnchor, -// typeValidator, -// id, -// /* Below from BaseJsonValidator */ -// suppressSubSchemaRetrieval, -// schemaNode, -// schemaContext, -// parentSchema, -// schemaLocation, -// evaluationPath, -// evaluationParentSchema, -// /* errorMessage */ null); -// -// } -// return this; -// } - public SchemaContext getSchemaContext() { return this.schemaContext; } + + public boolean hasKeyword(String keyword) { + return this.schemaNode.has(keyword); + } /** * Find the schema node for $ref attribute. @@ -461,7 +367,6 @@ public Schema getSubSchema(NodePath fragment) { Schema subSchema = null; JsonNode parentNode = parent.getSchemaNode(); SchemaLocation schemaLocation = document.getSchemaLocation(); - NodePath evaluationPath = document.getEvaluationPath(); int nameCount = fragment.getNameCount(); for (int x = 0; x < nameCount; x++) { /* @@ -478,10 +383,8 @@ public Schema getSubSchema(NodePath fragment) { if (segment instanceof Number && parentNode.isArray()) { int index = ((Number) segment).intValue(); schemaLocation = schemaLocation.append(index); - evaluationPath = evaluationPath.append(index); } else { schemaLocation = schemaLocation.append(segment.toString()); - evaluationPath = evaluationPath.append(segment.toString()); } /* * The parent schema context is used to create as there can be changes in @@ -492,7 +395,7 @@ public Schema getSubSchema(NodePath fragment) { // if (!("definitions".equals(segment.toString()) || "$defs".equals(segment.toString()) // )) { if (id != null || x == nameCount - 1) { - subSchema = parent.getSchemaContext().newSchema(schemaLocation, evaluationPath, subSchemaNode, + subSchema = parent.getSchemaContext().newSchema(schemaLocation, subSchemaNode, parent); parent = subSchema; schemaLocation = subSchema.getSchemaLocation(); @@ -516,7 +419,7 @@ public Schema getSubSchema(NodePath fragment) { .message("Reference {0} cannot be resolved") .instanceLocation(schemaLocation.getFragment()) .schemaLocation(schemaLocation) - .evaluationPath(evaluationPath) + .evaluationPath(null) .arguments(fragment).build(); throw new InvalidSchemaRefException(error); } @@ -605,14 +508,12 @@ private List read(JsonNode schemaNode) { if (schemaNode.isBoolean()) { validators = new ArrayList<>(1); if (schemaNode.booleanValue()) { - NodePath path = getEvaluationPath().append("true"); - KeywordValidator validator = this.schemaContext.newValidator(getSchemaLocation().append("true"), path, + KeywordValidator validator = this.schemaContext.newValidator(getSchemaLocation().append("true"), "true", schemaNode, this); validators.add(validator); } else { - NodePath path = getEvaluationPath().append("false"); KeywordValidator validator = this.schemaContext.newValidator(getSchemaLocation().append("false"), - path, "false", schemaNode, this); + "false", schemaNode, this); validators.add(validator); } } else { @@ -625,7 +526,6 @@ private List read(JsonNode schemaNode) { String pname = entry.getKey(); JsonNode nodeToUse = entry.getValue(); - NodePath path = getEvaluationPath().append(pname); SchemaLocation schemaPath = getSchemaLocation().append(pname); if ("$recursiveAnchor".equals(pname)) { @@ -634,8 +534,8 @@ private List read(JsonNode schemaNode) { .messageKey("internal.invalidRecursiveAnchor") .message( "The value of a $recursiveAnchor must be a Boolean literal but is {0}") - .instanceLocation(path) - .evaluationPath(path) + .instanceLocation(null) + .evaluationPath(null) .schemaLocation(schemaPath) .arguments(nodeToUse.getNodeType().toString()) .build(); @@ -644,17 +544,18 @@ private List read(JsonNode schemaNode) { this.recursiveAnchor = nodeToUse.booleanValue(); } - KeywordValidator validator = this.schemaContext.newValidator(schemaPath, path, + KeywordValidator validator = this.schemaContext.newValidator(schemaPath, pname, nodeToUse, this); if (validator != null) { validators.add(validator); + if ("unevaluatedProperties".equals(pname)) { + this.unevaluatedPropertiesPresent = true; + } else if ("unevaluatedItems".equals(pname)) { + this.unevaluatedItemsPresent = true; + } if ("$ref".equals(pname)) { refValidator = validator; - } else if ("type".equals(pname)) { - if (validator instanceof TypeValidator) { - this.typeValidator = (TypeValidator) validator; - } } } @@ -677,8 +578,8 @@ private List read(JsonNode schemaNode) { * so that we can apply default values before validating required. */ private static final Comparator VALIDATOR_SORT = (lhs, rhs) -> { - String lhsName = lhs.getEvaluationPath().getName(-1); - String rhsName = rhs.getEvaluationPath().getName(-1); + String lhsName = lhs.getKeyword(); + String rhsName = rhs.getKeyword(); if (lhsName.equals(rhsName)) return 0; @@ -686,6 +587,9 @@ private List read(JsonNode schemaNode) { if (lhsName.equals("discriminator")) return -1; if (rhsName.equals("discriminator")) return 1; + if (lhsName.equals("type")) return -1; + if (rhsName.equals("type")) return 1; + if (lhsName.equals("properties")) return -1; if (rhsName.equals("properties")) return 1; if (lhsName.equals("patternProperties")) return -1; @@ -702,14 +606,46 @@ private List read(JsonNode schemaNode) { @Override public void validate(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, NodePath instanceLocation) { - int currentErrors = executionContext.getErrors().size(); - for (KeywordValidator v : getValidators()) { - v.validate(executionContext, jsonNode, rootNode, instanceLocation); + List validators = getValidators(); // Load the validators before checking the flags + executionContext.evaluationSchema.addLast(this); + boolean unevaluatedPropertiesPresent = executionContext.unevaluatedPropertiesPresent; + boolean unevaluatedItemsPresent = executionContext.unevaluatedItemsPresent; + if (this.unevaluatedPropertiesPresent) { + executionContext.unevaluatedPropertiesPresent = this.unevaluatedPropertiesPresent; + } + if (this.unevaluatedItemsPresent) { + executionContext.unevaluatedItemsPresent = this.unevaluatedItemsPresent; } - if (executionContext.getErrors().size() > currentErrors) { - // Failed with assertion set result and drop all annotations from this schema - // and all subschemas - executionContext.getInstanceResults().setResult(instanceLocation, getSchemaLocation(), getEvaluationPath(), false); + try { + int currentErrors = executionContext.getErrors().size(); + for (KeywordValidator v : validators) { + executionContext.evaluationPathAddLast(v.getKeyword()); + executionContext.evaluationSchemaPath.addLast(v.getKeyword()); + try { + v.validate(executionContext, jsonNode, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationSchemaPath.removeLast(); + } + } + if (executionContext.getErrors().size() > currentErrors) { + // Failed with assertion set result and drop all annotations from this schema + // and all subschemas + List annotations = executionContext.getAnnotations().asMap().get(instanceLocation); + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.getEvaluationPath().startsWith(executionContext.getEvaluationPath())) { + annotation.setValid(false); + } + } + } + + //executionContext.getInstanceResults().setResult(instanceLocation, getSchemaLocation(), executionContext.getEvaluationPath(), false); + } + } finally { + executionContext.evaluationSchema.removeLast(); + executionContext.unevaluatedPropertiesPresent = unevaluatedPropertiesPresent; + executionContext.unevaluatedItemsPresent = unevaluatedItemsPresent; } } @@ -1568,6 +1504,7 @@ private T walkAtNodeInternal(ExecutionContext executionContext, JsonNode nod executionCustomizer.customize(executionContext, this.schemaContext); } // Walk through the schema. + executionContext.evaluationPath = atRoot(); walk(executionContext, node, rootNode, instanceLocation, validate); return format.format(this, executionContext, this.schemaContext); } @@ -1576,44 +1513,53 @@ private T walkAtNodeInternal(ExecutionContext executionContext, JsonNode nod public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { // Walk through all the JSONWalker's. - int currentErrors = executionContext.getErrors().size(); - for (KeywordValidator validator : getValidators()) { - NodePath 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 (executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, - evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this, validator)) { - validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + List validators = getValidators(); // Load the validators before checking the flags + executionContext.evaluationSchema.addLast(this); + boolean unevaluatedPropertiesPresent = executionContext.unevaluatedPropertiesPresent; + boolean unevaluatedItemsPresent = executionContext.unevaluatedItemsPresent; + if (this.unevaluatedPropertiesPresent) { + executionContext.unevaluatedPropertiesPresent = this.unevaluatedPropertiesPresent; + } + if (this.unevaluatedItemsPresent) { + executionContext.unevaluatedItemsPresent = this.unevaluatedItemsPresent; + } + try { + int currentErrors = executionContext.getErrors().size(); + for (KeywordValidator validator : validators) { + try { + // Call all the pre-walk listeners. If at least one of the pre walk listeners + // returns SKIP, then skip the walk. + if (executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPreWalkListeners(executionContext, + validator.getKeyword(), node, rootNode, instanceLocation, + this, validator)) { + executionContext.evaluationPathAddLast(validator.getKeyword()); + executionContext.evaluationSchemaPath.addLast(validator.getKeyword()); + try { + validator.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationSchemaPath.removeLast(); + } + } + } finally { + // Call all the post-walk listeners. + executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, + validator.getKeyword(), node, rootNode, instanceLocation, + this, validator, + executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); } - } finally { - // Call all the post-walk listeners. - executionContext.getWalkConfig().getKeywordWalkListenerRunner().runPostWalkListeners(executionContext, - evaluationPathWithKeyword.getName(-1), node, rootNode, instanceLocation, - this, validator, - executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); } + } finally { + executionContext.evaluationSchema.removeLast(); + executionContext.unevaluatedPropertiesPresent = unevaluatedPropertiesPresent; + executionContext.unevaluatedItemsPresent = unevaluatedItemsPresent; } } /************************ END OF WALK METHODS **********************************/ @Override public String toString() { - return "\"" + getEvaluationPath() + "\" : " + getSchemaNode().toString(); - } - - public boolean hasTypeValidator() { - return getTypeValidator() != null; - } - - public TypeValidator getTypeValidator() { - // As the validators are lazy loaded the typeValidator is only known if the - // validators are not null - if (this.validators == null) { - getValidators(); - } - return this.typeValidator; + return getSchemaNode().toString(); } public List getValidators() { @@ -1634,15 +1580,23 @@ public List getValidators() { */ public void initializeValidators() { if (!this.validatorsLoaded) { - for (final KeywordValidator validator : getValidators()) { - validator.preloadSchema(); - } /* - * This is only set to true after the preload as it may throw an exception for - * instance if the remote host is unavailable and we may want to be able to try - * again. + * This is set to true here to prevent recursive cyclic loading of the validators */ this.validatorsLoaded = true; + try { + for (final KeywordValidator validator : getValidators()) { + validator.preloadSchema(); + } + } catch (RuntimeException e) { + /* + * As the preload may throw an exception for + * instance if the remote host is unavailable and we may want to be able to try + * again. + */ + this.validatorsLoaded = false; + throw e; + } } } diff --git a/src/main/java/com/networknt/schema/SchemaContext.java b/src/main/java/com/networknt/schema/SchemaContext.java index 7ff84923d..783ff91ac 100644 --- a/src/main/java/com/networknt/schema/SchemaContext.java +++ b/src/main/java/com/networknt/schema/SchemaContext.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.dialect.Dialect; import com.networknt.schema.keyword.KeywordValidator; -import com.networknt.schema.path.NodePath; /** * The schema context associated with a schema and all its validators. @@ -118,13 +117,13 @@ public SchemaContext(Dialect dialect, SchemaRegistry schemaRegistry, } } - public Schema newSchema(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema) { - return getSchemaRegistry().create(this, schemaLocation, evaluationPath, schemaNode, parentSchema); + public Schema newSchema(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema) { + return getSchemaRegistry().create(this, schemaLocation, schemaNode, parentSchema); } - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, + public KeywordValidator newValidator(SchemaLocation schemaLocation, String keyword /* keyword */, JsonNode schemaNode, Schema parentSchema) { - return this.dialect.newValidator(this, schemaLocation, evaluationPath, keyword, schemaNode, parentSchema); + return this.dialect.newValidator(this, schemaLocation, keyword, schemaNode, parentSchema); } public String resolveSchemaId(JsonNode schemaNode) { diff --git a/src/main/java/com/networknt/schema/SchemaLocation.java b/src/main/java/com/networknt/schema/SchemaLocation.java index ecbbb3099..82f7cbe7c 100644 --- a/src/main/java/com/networknt/schema/SchemaLocation.java +++ b/src/main/java/com/networknt/schema/SchemaLocation.java @@ -22,6 +22,7 @@ import com.networknt.schema.path.NodePath; import com.networknt.schema.path.PathType; +import com.networknt.schema.utils.Strings; /** * The schema location is the canonical IRI of the schema object plus a JSON @@ -219,7 +220,8 @@ public static NodePath of(String fragmentString) { fragmentString = fragmentString.substring(1); } NodePath fragment = JSON_POINTER; - String[] fragmentParts = fragmentString.split("/"); +// String[] fragmentParts = fragmentString.split("/"); + String[] fragmentParts = Strings.split(fragmentString, '/'); boolean jsonPointer = false; if (fragmentString.startsWith("/")) { @@ -270,10 +272,6 @@ public static NodePath of(String fragmentString) { fragment = fragment.append(fragmentPartString); } } - if (index == -1 && fragmentString.endsWith("/")) { - // Trailing / in fragment - fragment = fragment.append(""); - } return fragment; } diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index dfcfb80d9..0b8af9432 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -22,7 +22,7 @@ import com.networknt.schema.dialect.Dialect; import com.networknt.schema.dialect.DialectId; import com.networknt.schema.dialect.DialectRegistry; -import com.networknt.schema.path.NodePath; +import com.networknt.schema.resource.InputStreamSource; import com.networknt.schema.resource.ResourceLoaders; import com.networknt.schema.resource.SchemaIdResolvers; import com.networknt.schema.resource.SchemaLoader; @@ -33,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -454,7 +455,7 @@ public SchemaLoader getSchemaLoader() { protected Schema newSchema(SchemaLocation schemaUri, JsonNode schemaNode) { final SchemaContext schemaContext = createSchemaContext(schemaNode); Schema jsonSchema = doCreate(schemaContext, getSchemaLocation(schemaUri), - new NodePath(schemaContext.getSchemaRegistryConfig().getPathType()), schemaNode, null, false); + schemaNode, null, false); preload(jsonSchema); return jsonSchema; } @@ -483,14 +484,14 @@ private void preload(Schema schema) { } } - public Schema create(SchemaContext schemaContext, SchemaLocation schemaLocation, NodePath evaluationPath, + public Schema create(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema) { - return doCreate(schemaContext, schemaLocation, evaluationPath, schemaNode, parentSchema, false); + return doCreate(schemaContext, schemaLocation, schemaNode, parentSchema, false); } - private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, NodePath evaluationPath, + private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, boolean suppressSubSchemaRetrieval) { - return Schema.from(withDialect(schemaContext, schemaNode), schemaLocation, evaluationPath, schemaNode, + return Schema.from(withDialect(schemaContext, schemaNode), schemaLocation, schemaNode, parentSchema, suppressSubSchemaRetrieval); } @@ -680,38 +681,42 @@ public Schema loadSchema(final SchemaLocation schemaUri) { } protected Schema getMappedSchema(final SchemaLocation schemaUri) { - try (InputStream inputStream = this.schemaLoader.getSchemaResource(schemaUri.getAbsoluteIri()) - .getInputStream()) { - if (inputStream == null) { - throw new IOException("Cannot load schema at " + schemaUri); - } - final JsonNode schemaNode; - if (isYaml(schemaUri)) { - schemaNode = readTree(inputStream, InputFormat.YAML); - } else { - schemaNode = readTree(inputStream, InputFormat.JSON); - } + InputStreamSource inputStreamSource = this.schemaLoader.getSchemaResource(schemaUri.getAbsoluteIri()); + if (inputStreamSource != null) { + try (InputStream inputStream = inputStreamSource.getInputStream()) { + if (inputStream == null) { + throw new IOException("Cannot load schema at " + schemaUri); + } + final JsonNode schemaNode; + if (isYaml(schemaUri)) { + schemaNode = readTree(inputStream, InputFormat.YAML); + } else { + schemaNode = readTree(inputStream, InputFormat.JSON); + } - final Dialect dialect = getDialectOrDefault(schemaNode); - NodePath evaluationPath = new NodePath(getSchemaRegistryConfig().getPathType()); - if (schemaUri.getFragment() == null || schemaUri.getFragment().getNameCount() == 0) { - // Schema without fragment - SchemaContext schemaContext = new SchemaContext(dialect, this); - return doCreate(schemaContext, schemaUri, evaluationPath, schemaNode, null, - true /* retrieved via id, resolving will not change anything */); - } else { - // Schema with fragment pointing to sub schema - final SchemaContext schemaContext = createSchemaContext(schemaNode); - SchemaLocation documentLocation = new SchemaLocation(schemaUri.getAbsoluteIri()); - Schema document = doCreate(schemaContext, documentLocation, evaluationPath, schemaNode, null, false); - return document.getRefSchema(schemaUri.getFragment()); + final Dialect dialect = getDialectOrDefault(schemaNode); + if (schemaUri.getFragment() == null || schemaUri.getFragment().getNameCount() == 0) { + // Schema without fragment + SchemaContext schemaContext = new SchemaContext(dialect, this); + return doCreate(schemaContext, schemaUri, schemaNode, null, + true /* retrieved via id, resolving will not change anything */); + } else { + // Schema with fragment pointing to sub schema + final SchemaContext schemaContext = createSchemaContext(schemaNode); + SchemaLocation documentLocation = new SchemaLocation(schemaUri.getAbsoluteIri()); + Schema document = doCreate(schemaContext, documentLocation, schemaNode, null, + false); + return document.getRefSchema(schemaUri.getFragment()); + } + } catch (IOException e) { + logger.error("Failed to load json schema from {}", schemaUri.getAbsoluteIri(), e); + SchemaException exception = new SchemaException( + "Failed to load json schema from " + schemaUri.getAbsoluteIri()); + exception.initCause(e); + throw exception; } - } catch (IOException e) { - logger.error("Failed to load json schema from {}", schemaUri.getAbsoluteIri(), e); - SchemaException exception = new SchemaException( - "Failed to load json schema from " + schemaUri.getAbsoluteIri()); - exception.initCause(e); - throw exception; + } else { + throw new SchemaException(new FileNotFoundException(schemaUri.getAbsoluteIri().toString())); } } diff --git a/src/main/java/com/networknt/schema/SchemaRegistryConfig.java b/src/main/java/com/networknt/schema/SchemaRegistryConfig.java index 053f03a62..65f049d4a 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistryConfig.java +++ b/src/main/java/com/networknt/schema/SchemaRegistryConfig.java @@ -46,8 +46,6 @@ public static SchemaRegistryConfig getInstance() { return Holder.INSTANCE; } - public static final int DEFAULT_PRELOAD_SCHEMA_REF_MAX_NESTING_DEPTH = 40; - /** * The execution context customizer that runs by default for all schemas. */ @@ -75,12 +73,6 @@ public static SchemaRegistryConfig getInstance() { */ private final Boolean formatAssertionsEnabled; - /** - * When set to true, use Java-specific semantics rather than native JavaScript - * semantics - */ - private final boolean javaSemantics; - /** * The Locale to consider when loading validation messages from the default resource bundle. */ @@ -106,11 +98,6 @@ public static SchemaRegistryConfig getInstance() { */ private final boolean preloadSchema; - /** - * Controls the max depth of the evaluation path to preload when preloading refs. - */ - private final int preloadSchemaRefMaxNestingDepth; - /** * Used to create {@link com.networknt.schema.regex.RegularExpression}. */ @@ -139,10 +126,9 @@ public static SchemaRegistryConfig getInstance() { protected SchemaRegistryConfig(boolean cacheRefs, String errorMessageKeyword, ExecutionContextCustomizer executionContextCustomizer, boolean failFast, Boolean formatAssertionsEnabled, - boolean javaSemantics, Locale locale, boolean losslessNarrowing, MessageSource messageSource, PathType pathType, - boolean preloadSchema, int preloadSchemaRefMaxNestingDepth, + boolean preloadSchema, RegularExpressionFactory regularExpressionFactory, SchemaIdValidator schemaIdValidator, Map strictness, boolean typeLoose) { super(); @@ -151,13 +137,11 @@ protected SchemaRegistryConfig(boolean cacheRefs, this.executionContextCustomizer = executionContextCustomizer; this.failFast = failFast; this.formatAssertionsEnabled = formatAssertionsEnabled; - this.javaSemantics = javaSemantics; this.locale = locale; this.losslessNarrowing = losslessNarrowing; this.messageSource = messageSource; this.pathType = pathType; this.preloadSchema = preloadSchema; - this.preloadSchemaRefMaxNestingDepth = preloadSchemaRefMaxNestingDepth; this.regularExpressionFactory = regularExpressionFactory; this.schemaIdValidator = schemaIdValidator; this.strictness = strictness; @@ -221,15 +205,6 @@ public PathType getPathType() { return this.pathType; } - /** - * Gets the max depth of the evaluation path to preload when preloading refs. - * - * @return the max depth to preload - */ - public int getPreloadSchemaRefMaxNestingDepth() { - return preloadSchemaRefMaxNestingDepth; - } - /** * Gets the regular expression factory. *

@@ -269,10 +244,6 @@ public boolean isFailFast() { return this.failFast; } - public boolean isJavaSemantics() { - return this.javaSemantics; - } - public boolean isLosslessNarrowing() { return this.losslessNarrowing; } @@ -346,13 +317,11 @@ public static Builder builder(SchemaRegistryConfig config) { builder.executionContextCustomizer = config.executionContextCustomizer; builder.failFast = config.failFast; builder.formatAssertionsEnabled = config.formatAssertionsEnabled; - builder.javaSemantics = config.javaSemantics; builder.locale = config.locale; builder.losslessNarrowing = config.losslessNarrowing; builder.messageSource = config.messageSource; builder.pathType = config.pathType; builder.preloadSchema = config.preloadSchema; - builder.preloadSchemaRefMaxNestingDepth = config.preloadSchemaRefMaxNestingDepth; builder.regularExpressionFactory = config.regularExpressionFactory; builder.schemaIdValidator = config.schemaIdValidator; builder.strictness = config.strictness; @@ -379,13 +348,11 @@ public static abstract class BuilderSupport { protected ExecutionContextCustomizer executionContextCustomizer = null; protected boolean failFast = false; protected Boolean formatAssertionsEnabled = null; - protected boolean javaSemantics = false; protected Locale locale = null; // This must be null to use Locale.getDefault() as the default can be changed protected boolean losslessNarrowing = false; protected MessageSource messageSource = null; protected PathType pathType = PathType.JSON_POINTER; protected boolean preloadSchema = true; - protected int preloadSchemaRefMaxNestingDepth = DEFAULT_PRELOAD_SCHEMA_REF_MAX_NESTING_DEPTH; protected RegularExpressionFactory regularExpressionFactory = JDKRegularExpressionFactory.getInstance(); protected SchemaIdValidator schemaIdValidator = SchemaIdValidator.DEFAULT; protected Map strictness = new HashMap<>(0); @@ -459,12 +426,7 @@ public T formatAssertionsEnabled(Boolean formatAssertionsEnabled) { return self(); } - public T javaSemantics(boolean javaSemantics) { - this.javaSemantics = javaSemantics; - return self(); - } - - /** + /** * Set the locale to consider when generating localized messages. *

* Note that this locale is set on a schema registry basis. To configure the @@ -480,7 +442,16 @@ public T locale(Locale locale) { this.locale = locale; return self(); } - + /** + * Sets whether lossless narrowing of other numeric types to integer is performed. + *

+ * Note that this likely only has a visible effect for dialects written before Draft 6. + *

+ * Since Draft 6 for example 1.0 is treated as an integer even without this enabled. + * + * @param losslessNarrowing true to enable + * @return the builder + */ public T losslessNarrowing(boolean losslessNarrowing) { this.losslessNarrowing = losslessNarrowing; return self(); @@ -519,18 +490,7 @@ public T preloadSchema(boolean preloadSchema) { this.preloadSchema = preloadSchema; return self(); } - /** - * Sets the max depth of the evaluation path to preload when preloading refs. - *

- * Defaults to 40. - * - * @param preloadSchemaRefMaxNestingDepth to preload - * @return the builder - */ - public T preloadSchemaRefMaxNestingDepth(int preloadSchemaRefMaxNestingDepth) { - this.preloadSchemaRefMaxNestingDepth = preloadSchemaRefMaxNestingDepth; - return self(); - } + /** * Sets the regular expression factory. *

@@ -572,13 +532,11 @@ public T typeLoose(boolean typeLoose) { this.typeLoose = typeLoose; return self(); } + public SchemaRegistryConfig build() { - return new SchemaRegistryConfig(cacheRefs, errorMessageKeyword, - executionContextCustomizer, failFast, formatAssertionsEnabled, - javaSemantics, locale, losslessNarrowing, messageSource, - pathType, preloadSchema, preloadSchemaRefMaxNestingDepth, - regularExpressionFactory, schemaIdValidator, strictness, typeLoose - ); + return new SchemaRegistryConfig(cacheRefs, errorMessageKeyword, executionContextCustomizer, failFast, + formatAssertionsEnabled, locale, losslessNarrowing, messageSource, pathType, + preloadSchema, regularExpressionFactory, schemaIdValidator, strictness, typeLoose); } } diff --git a/src/main/java/com/networknt/schema/Validator.java b/src/main/java/com/networknt/schema/Validator.java index 84d295dc5..09434d990 100644 --- a/src/main/java/com/networknt/schema/Validator.java +++ b/src/main/java/com/networknt/schema/Validator.java @@ -63,13 +63,4 @@ default void walk(ExecutionContext executionContext, JsonNode instanceNode, Json * @return the schema location */ SchemaLocation getSchemaLocation(); - - /** - * The evaluation path is the set of keys, starting from the schema root, - * through which evaluation passes to reach the schema object that produced a - * specific result. - * - * @return the evaluation path - */ - NodePath getEvaluationPath(); } diff --git a/src/main/java/com/networknt/schema/annotation/Annotation.java b/src/main/java/com/networknt/schema/annotation/Annotation.java index a3638498d..7d59579d0 100644 --- a/src/main/java/com/networknt/schema/annotation/Annotation.java +++ b/src/main/java/com/networknt/schema/annotation/Annotation.java @@ -30,6 +30,7 @@ public class Annotation { private final SchemaLocation schemaLocation; private final NodePath evaluationPath; private final Object value; + private boolean valid = true; // If not valid it means dropped public Annotation(String keyword, NodePath instanceLocation, SchemaLocation schemaLocation, NodePath evaluationPath, Object value) { @@ -90,6 +91,14 @@ public T getValue() { return (T) value; } + public boolean isValid() { + return valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + @Override public String toString() { return "Annotation [evaluationPath=" + evaluationPath + ", schemaLocation=" + schemaLocation diff --git a/src/main/java/com/networknt/schema/dialect/Dialect.java b/src/main/java/com/networknt/schema/dialect/Dialect.java index ebe04b2bc..3fd4580c8 100644 --- a/src/main/java/com/networknt/schema/dialect/Dialect.java +++ b/src/main/java/com/networknt/schema/dialect/Dialect.java @@ -30,7 +30,6 @@ import com.networknt.schema.keyword.KeywordFactory; import com.networknt.schema.keyword.KeywordValidator; import com.networknt.schema.keyword.UnknownKeywordFactory; -import com.networknt.schema.path.NodePath; import com.networknt.schema.keyword.KeywordType; import com.networknt.schema.utils.Strings; import com.networknt.schema.vocabulary.Vocabularies; @@ -441,14 +440,13 @@ public SpecificationVersion getSpecificationVersion() { * * @param schemaContext the schema context * @param schemaLocation the schema location - * @param evaluationPath the evaluation path * @param keyword the keyword * @param schemaNode the schema node * @param parentSchema the parent schema * @return the validator */ public KeywordValidator newValidator(SchemaContext schemaContext, SchemaLocation schemaLocation, - NodePath evaluationPath, String keyword, JsonNode schemaNode, Schema parentSchema) { + String keyword, JsonNode schemaNode, Schema parentSchema) { try { Keyword kw = this.keywords.get(keyword); if (kw == null) { @@ -460,7 +458,7 @@ public KeywordValidator newValidator(SchemaContext schemaContext, SchemaLocation } if (KeywordType.DISCRIMINATOR.getValue().equals(keyword) && schemaContext.isDiscriminatorKeywordEnabled()) { - return KeywordType.DISCRIMINATOR.newValidator(schemaLocation, evaluationPath, schemaNode, + return KeywordType.DISCRIMINATOR.newValidator(schemaLocation, schemaNode, parentSchema, schemaContext); } kw = this.builder.unknownKeywordFactory != null @@ -470,7 +468,7 @@ public KeywordValidator newValidator(SchemaContext schemaContext, SchemaLocation return null; } } - return kw.newValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, schemaContext); + return kw.newValidator(schemaLocation, schemaNode, parentSchema, schemaContext); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof SchemaException) { logger.error("Error:", e); diff --git a/src/main/java/com/networknt/schema/format/BaseFormatValidator.java b/src/main/java/com/networknt/schema/format/BaseFormatValidator.java index 31cda4d2b..b22e17b2b 100644 --- a/src/main/java/com/networknt/schema/format/BaseFormatValidator.java +++ b/src/main/java/com/networknt/schema/format/BaseFormatValidator.java @@ -10,15 +10,14 @@ import com.networknt.schema.SpecificationVersion; import com.networknt.schema.keyword.BaseKeywordValidator; import com.networknt.schema.keyword.Keyword; -import com.networknt.schema.path.NodePath; public abstract class BaseFormatValidator extends BaseKeywordValidator { protected final boolean assertionsEnabled; - public BaseFormatValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public BaseFormatValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, Keyword keyword, SchemaContext schemaContext) { - super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext); SpecificationVersion dialect = this.schemaContext.getDialect().getSpecificationVersion(); if (dialect == null || dialect.getOrder() < SpecificationVersion.DRAFT_2019_09.getOrder()) { assertionsEnabled = true; diff --git a/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java b/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java index 9de8740fe..70d323bf3 100644 --- a/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java +++ b/src/main/java/com/networknt/schema/keyword/AbstractKeywordValidator.java @@ -16,13 +16,14 @@ package com.networknt.schema.keyword; +import java.util.Iterator; import java.util.function.Consumer; import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.ExecutionContext; +import com.networknt.schema.Schema; import com.networknt.schema.SchemaLocation; import com.networknt.schema.annotation.Annotation; -import com.networknt.schema.path.NodePath; /** * Abstract {@link KeywordValidator}. @@ -32,20 +33,16 @@ public abstract class AbstractKeywordValidator implements KeywordValidator { protected final JsonNode schemaNode; protected final SchemaLocation schemaLocation; - protected final NodePath evaluationPath; - /** * Constructor. * @param keyword the keyword * @param schemaNode the schema node * @param schemaLocation the schema location - * @param evaluationPath the evaluation path */ - public AbstractKeywordValidator(String keyword, JsonNode schemaNode, SchemaLocation schemaLocation, NodePath evaluationPath) { + public AbstractKeywordValidator(String keyword, JsonNode schemaNode, SchemaLocation schemaLocation) { this.keyword = keyword; this.schemaNode = schemaNode; this.schemaLocation = schemaLocation; - this.evaluationPath = evaluationPath; } /** @@ -53,10 +50,9 @@ public AbstractKeywordValidator(String keyword, JsonNode schemaNode, SchemaLocat * @param keyword the keyword * @param schemaNode the schema node * @param schemaLocation the schema location - * @param evaluationPath the evaluation path */ - public AbstractKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation schemaLocation, NodePath evaluationPath) { - this(keyword.getValue(), schemaNode, schemaLocation, evaluationPath); + public AbstractKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation schemaLocation) { + this(keyword.getValue(), schemaNode, schemaLocation); } @Override @@ -64,11 +60,6 @@ public SchemaLocation getSchemaLocation() { return schemaLocation; } - @Override - public NodePath getEvaluationPath() { - return evaluationPath; - } - @Override public String getKeyword() { return keyword; @@ -85,7 +76,7 @@ public JsonNode getSchemaNode() { @Override public String toString() { - return getEvaluationPath().getName(-1); + return getKeyword(); } /** @@ -117,9 +108,73 @@ protected boolean collectAnnotations(ExecutionContext executionContext, String k * @param customizer to customize the annotation */ protected void putAnnotation(ExecutionContext executionContext, Consumer customizer) { - Annotation.Builder builder = Annotation.builder().evaluationPath(this.evaluationPath) + Annotation.Builder builder = Annotation.builder().evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.schemaLocation).keyword(getKeyword()); customizer.accept(builder); executionContext.getAnnotations().put(builder.build()); } + + + + /** + * Determines if the keyword exists adjacent in the evaluation path. + *

+ * This does not check if the keyword exists in the current meta schema as this + * can be a cross-draft case where the properties keyword is in a Draft 7 schema + * and the unevaluatedProperties keyword is in an outer Draft 2020-12 schema. + *

+ * The fact that the validator exists in the evaluation path implies that the + * keyword was valid in whatever meta schema for that schema it was created for. + * + * @param keyword the keyword to check + * @return true if found + */ + protected boolean hasAdjacentKeywordInEvaluationPath(ExecutionContext executionContext, String keyword) { + Iterator evaluationSchemaPathIterator = executionContext.getEvaluationSchemaPath().descendingIterator(); + Iterator evaluationSchemaIterator = executionContext.getEvaluationSchema().descendingIterator(); + boolean stop = false; + + // Skip the first as this is the path pointing to the current keyword eg. properties eg /$ref/properties + // What is needed is the evaluationPath pointing to the current evaluationSchema eg /$ref + if (evaluationSchemaPathIterator.hasNext()) { + evaluationSchemaPathIterator.next(); + } + + while (evaluationSchemaIterator.hasNext()) { + Schema schema = evaluationSchemaIterator.next(); + boolean hasKeyword = schema.getSchemaNode().has(keyword); + if (hasKeyword) { + return true; + } + if (stop) { + return false; + } + if (evaluationSchemaPathIterator.hasNext()) { + Object evaluationPath = evaluationSchemaPathIterator.next(); + if ("properties".equals(evaluationPath) || "items".equals(evaluationPath)) { + // If there is a change in instance location then after the next schema + // stop + stop = true; + } + } + } + return false; + } + + protected boolean hasUnevaluatedItemsInEvaluationPath(ExecutionContext executionContext) { + if (executionContext.isUnevaluatedItemsPresent() + && hasAdjacentKeywordInEvaluationPath(executionContext, "unevaluatedItems")) { + return true; + } + return false; + } + + protected boolean hasUnevaluatedPropertiesInEvaluationPath(ExecutionContext executionContext) { + if (executionContext.isUnevaluatedPropertiesPresent() + && hasAdjacentKeywordInEvaluationPath(executionContext, "unevaluatedProperties")) { + return true; + } + return false; + } + } diff --git a/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java index 6359ef392..37a53d749 100644 --- a/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/AdditionalPropertiesValidator.java @@ -43,17 +43,15 @@ public class AdditionalPropertiesValidator extends BaseKeywordValidator { private final Set allowedProperties; private final List patternProperties; - private Boolean hasUnevaluatedPropertiesValidator; - - public AdditionalPropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, + public AdditionalPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ADDITIONAL_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.ADDITIONAL_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.isBoolean()) { allowAdditionalProperties = schemaNode.booleanValue(); additionalPropertiesSchema = null; } else if (schemaNode.isObject()) { allowAdditionalProperties = true; - additionalPropertiesSchema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + additionalPropertiesSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } else { allowAdditionalProperties = false; additionalPropertiesSchema = null; @@ -95,7 +93,8 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo Set matchedInstancePropertyNames = null; - boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext); + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) + || collectAnnotations(executionContext); // if allowAdditionalProperties is true, add all the properties as evaluated. if (allowAdditionalProperties && collectAnnotations) { for (Iterator it = node.fieldNames(); it.hasNext();) { @@ -118,6 +117,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo if (!allowAdditionalProperties) { executionContext.addError(error().instanceNode(node).property(pname) .instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) .locale(executionContext.getExecutionConfig().getLocale()) .arguments(pname).build()); } else { @@ -135,7 +135,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo } if (collectAnnotations) { executionContext.getAnnotations().put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation).keyword(getKeyword()) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation).keyword(getKeyword()) .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames : Collections.emptySet()) .build()); } @@ -180,22 +180,10 @@ private boolean handledByPatternProperties(String pname) { return false; } - private boolean collectAnnotations() { - return hasUnevaluatedPropertiesValidator(); - } - - private boolean hasUnevaluatedPropertiesValidator() { - if (this.hasUnevaluatedPropertiesValidator == null) { - this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties"); - } - return hasUnevaluatedPropertiesValidator; - } - @Override public void preloadSchema() { if(additionalPropertiesSchema != null) { additionalPropertiesSchema.initializeValidators(); } - collectAnnotations(); // cache the flag } } diff --git a/src/main/java/com/networknt/schema/keyword/AllOfValidator.java b/src/main/java/com/networknt/schema/keyword/AllOfValidator.java index 279cbd62b..c0d871d2d 100644 --- a/src/main/java/com/networknt/schema/keyword/AllOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/AllOfValidator.java @@ -35,9 +35,9 @@ public class AllOfValidator extends BaseKeywordValidator { private final List schemas; - public AllOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public AllOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ALL_OF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.ALL_OF, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isArray()) { JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) @@ -46,7 +46,7 @@ public AllOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, Js int size = schemaNode.size(); this.schemas = new ArrayList<>(size); for (int i = 0; i < size; i++) { - this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), schemaNode.get(i), parentSchema)); } } @@ -59,12 +59,19 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean walk) { + int schemaIndex = 0; for (Schema schema : this.schemas) { - if (!walk) { - schema.validate(executionContext, node, rootNode, instanceLocation); - } else { - schema.walk(executionContext, node, rootNode, instanceLocation, true); + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); } + schemaIndex++; } } @@ -75,9 +82,16 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root validate(executionContext, node, rootNode, instanceLocation, true); return; } + int schemaIndex = 0; for (Schema schema : this.schemas) { // Walk through the schema - schema.walk(executionContext, node, rootNode, instanceLocation, false); + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; } } diff --git a/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java b/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java index 1a0b17af0..6c961b973 100644 --- a/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java +++ b/src/main/java/com/networknt/schema/keyword/AnnotationKeyword.java @@ -29,9 +29,9 @@ public class AnnotationKeyword extends AbstractKeyword { private static final class Validator extends AbstractKeywordValidator { - public Validator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public Validator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext, Keyword keyword) { - super(keyword, schemaNode, schemaLocation, evaluationPath); + super(keyword, schemaNode, schemaLocation); } @Override @@ -62,8 +62,8 @@ public AnnotationKeyword(String keyword) { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - return new Validator(schemaLocation, evaluationPath, schemaNode, parentSchema, schemaContext, this); + return new Validator(schemaLocation, schemaNode, parentSchema, schemaContext, this); } } diff --git a/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java b/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java index 45c1bf72a..d2b722ffc 100644 --- a/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/AnyOfValidator.java @@ -36,11 +36,9 @@ public class AnyOfValidator extends BaseKeywordValidator { private final List schemas; - private Boolean canShortCircuit = null; - - public AnyOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public AnyOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ANY_OF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.ANY_OF, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isArray()) { JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) @@ -49,7 +47,7 @@ public AnyOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, Js int size = schemaNode.size(); this.schemas = new ArrayList<>(size); for (int i = 0; i < size; i++) { - this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), schemaNode.get(i), parentSchema)); } } @@ -73,28 +71,21 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo // Save flag as nested schema evaluation shouldn't trigger fail fast boolean failFast = executionContext.isFailFast(); try { + int schemaIndex = 0; executionContext.setFailFast(false); for (Schema schema : this.schemas) { subSchemaErrors.clear(); // Reuse and clear for each run - TypeValidator typeValidator = schema.getTypeValidator(); - if (typeValidator != null) { - // If schema has type validator and node type doesn't match with schemaType then - // ignore it - // For union type, it is a must to call TypeValidator - if (typeValidator.getSchemaType() != JsonType.UNION && !typeValidator.equalsToSchemaType(node)) { - typeValidator.validate(executionContext, node, rootNode, instanceLocation); - if (allErrors == null) { - allErrors = new ArrayList<>(); - } - allErrors.addAll(subSchemaErrors); - continue; + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); } + } finally { + executionContext.evaluationPathRemoveLast(); } - if (!walk) { - schema.validate(executionContext, node, rootNode, instanceLocation); - } else { - schema.walk(executionContext, node, rootNode, instanceLocation, true); - } + schemaIndex++; // check if any validation errors have occurred if (subSchemaErrors.isEmpty()) { @@ -103,7 +94,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo } if (subSchemaErrors.isEmpty() && (!this.schemaContext.isDiscriminatorKeywordEnabled()) - && canShortCircuit() && canShortCircuit(executionContext)) { + && canShortCircuit(executionContext)) { // Successful so return only the existing errors, ie. no new errors executionContext.setErrors(existingErrors); return; @@ -172,7 +163,7 @@ && canShortCircuit() && canShortCircuit(executionContext)) { // generating an assertion // if the discriminatingValue is not set in the payload existingErrors.add(error().keyword("discriminator").instanceNode(node) - .instanceLocation(instanceLocation).locale(executionContext.getExecutionConfig().getLocale()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .messageKey("discriminator.anyOf.no_match_found").arguments(state.getDiscriminatingValue()) .build()); } @@ -201,8 +192,15 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root validate(executionContext, node, rootNode, instanceLocation, true); return; } + int schemaIndex = 0; for (Schema schema : this.schemas) { - schema.walk(executionContext, node, rootNode, instanceLocation, false); + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; } } @@ -215,31 +213,17 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root * @return true if can short circuit */ protected boolean canShortCircuit(ExecutionContext executionContext) { - return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled(); - } - - /** - * If annotations are require for evaluation cannot short circuit. - * - * @return true if can short circuit - */ - protected boolean canShortCircuit() { - if (this.canShortCircuit == null) { - boolean canShortCircuit = true; - for (KeywordValidator validator : getEvaluationParentSchema().getValidators()) { - if ("unevaluatedProperties".equals(validator.getKeyword()) - || "unevaluatedItems".equals(validator.getKeyword())) { - canShortCircuit = false; - } - } - this.canShortCircuit = canShortCircuit; + if (hasUnevaluatedItemsInEvaluationPath(executionContext)) { + return false; + } + if (hasUnevaluatedPropertiesInEvaluationPath(executionContext)) { + return false; } - return this.canShortCircuit; + return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled(); } @Override public void preloadSchema() { preloadSchemas(this.schemas); - canShortCircuit(); // cache flag } } \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java b/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java index c58d4b8eb..ef13efb08 100644 --- a/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java +++ b/src/main/java/com/networknt/schema/keyword/BaseKeywordValidator.java @@ -21,7 +21,6 @@ import com.networknt.schema.Schema; import com.networknt.schema.MessageSourceError; import com.networknt.schema.SchemaLocation; -import com.networknt.schema.path.NodePath; import com.networknt.schema.SchemaContext; import java.util.Collection; @@ -36,12 +35,9 @@ public abstract class BaseKeywordValidator extends AbstractKeywordValidator { protected final Schema parentSchema; protected final Map errorMessage; - protected final Schema evaluationParentSchema; - public BaseKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation schemaLocation, - Schema parentSchema, SchemaContext schemaContext, - NodePath evaluationPath) { - super(keyword, schemaNode, schemaLocation, evaluationPath); + Schema parentSchema, SchemaContext schemaContext) { + super(keyword, schemaNode, schemaLocation); this.schemaContext = schemaContext; this.parentSchema = parentSchema; @@ -51,7 +47,6 @@ public BaseKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation } else { this.errorMessage = null; } - this.evaluationParentSchema = null; } /** @@ -61,8 +56,6 @@ public BaseKeywordValidator(Keyword keyword, JsonNode schemaNode, SchemaLocation * @param schemaLocation the schema location * @param schemaContext the schema context * @param parentSchema the parent schema - * @param evaluationPath the evaluation path - * @param evaluationParentSchema the evaluation parent schema * @param errorMessage the error message */ protected BaseKeywordValidator( @@ -71,16 +64,12 @@ protected BaseKeywordValidator( SchemaLocation schemaLocation, SchemaContext schemaContext, Schema parentSchema, - NodePath evaluationPath, - Schema evaluationParentSchema, Map errorMessage) { - super(keyword, schemaNode, schemaLocation, evaluationPath); + super(keyword, schemaNode, schemaLocation); this.schemaContext = schemaContext; this.parentSchema = parentSchema; this.errorMessage = errorMessage; - - this.evaluationParentSchema = evaluationParentSchema; } /** @@ -94,21 +83,6 @@ public Schema getParentSchema() { return this.parentSchema; } - /** - * Gets the evaluation parent schema. - *

- * This is the dynamic parent schema when following references. - * - * @see Schema#fromRef(Schema, NodePath) - * @return the evaluation parent schema - */ - public Schema getEvaluationParentSchema() { - if (this.evaluationParentSchema != null) { - return this.evaluationParentSchema; - } - return getParentSchema(); - } - protected String getNodeFieldType() { JsonNode typeField = this.getParentSchema().getSchemaNode().get("type"); if (typeField != null) { @@ -123,41 +97,10 @@ protected void preloadSchemas(final Collection schemas) { } } - /** - * Determines if the keyword exists adjacent in the evaluation path. - *

- * This does not check if the keyword exists in the current meta schema as this - * can be a cross-draft case where the properties keyword is in a Draft 7 schema - * and the unevaluatedProperties keyword is in an outer Draft 2020-12 schema. - *

- * The fact that the validator exists in the evaluation path implies that the - * keyword was valid in whatever meta schema for that schema it was created for. - * - * @param keyword the keyword to check - * @return true if found - */ - protected boolean hasAdjacentKeywordInEvaluationPath(String keyword) { - Schema schema = getEvaluationParentSchema(); - while (schema != null) { - for (KeywordValidator validator : schema.getValidators()) { - if (keyword.equals(validator.getKeyword())) { - return true; - } - } - Object element = schema.getEvaluationPath().getElement(-1); - if ("properties".equals(element) || "items".equals(element)) { - // If there is a change in instance location then return false - return false; - } - schema = schema.getEvaluationParentSchema(); - } - return false; - } - protected MessageSourceError.Builder error() { return MessageSourceError .builder(this.schemaContext.getSchemaRegistryConfig().getMessageSource(), this.errorMessage) - .schemaNode(this.schemaNode).schemaLocation(this.schemaLocation).evaluationPath(this.evaluationPath) + .schemaNode(this.schemaNode).schemaLocation(this.schemaLocation) .keyword(this.getKeyword()).messageKey(this.getKeyword()); } } diff --git a/src/main/java/com/networknt/schema/keyword/ConstValidator.java b/src/main/java/com/networknt/schema/keyword/ConstValidator.java index d1d1fec9f..e1c9f56bc 100644 --- a/src/main/java/com/networknt/schema/keyword/ConstValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ConstValidator.java @@ -26,22 +26,22 @@ * {@link KeywordValidator} for const. */ public class ConstValidator extends BaseKeywordValidator implements KeywordValidator { - public ConstValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public ConstValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.CONST, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.CONST, schemaNode, schemaLocation, parentSchema, schemaContext); } public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { if (schemaNode.isNumber() && node.isNumber()) { if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(schemaNode.asText(), node.asText()) .build()); } } else if (!schemaNode.equals(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText(), node.asText()).build()); + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()).arguments(schemaNode.asText(), node.asText()).build()); } } } diff --git a/src/main/java/com/networknt/schema/keyword/ContainsValidator.java b/src/main/java/com/networknt/schema/keyword/ContainsValidator.java index 381c72cbf..801f776d5 100644 --- a/src/main/java/com/networknt/schema/keyword/ContainsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ContainsValidator.java @@ -45,10 +45,8 @@ public class ContainsValidator extends BaseKeywordValidator { private final Integer min; private final Integer max; - private Boolean hasUnevaluatedItemsValidator = null; - - public ContainsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public ContainsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext); // Draft 6 added the contains keyword but maxContains and minContains first // appeared in Draft 2019-09 so the semantics of the validation changes @@ -58,7 +56,7 @@ public ContainsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, Integer currentMax = null; Integer currentMin = null; if (schemaNode.isObject() || schemaNode.isBoolean()) { - this.schema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); JsonNode parentSchemaNode = parentSchema.getSchemaNode(); Optional maxNode = Optional .ofNullable(parentSchemaNode.get(KeywordType.MAX_CONTAINS.getValue())) @@ -126,7 +124,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } } - boolean collectAnnotations = collectAnnotations(); + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); if (this.schema != null) { // This keyword produces an annotation value which is an array of the indexes to // which this keyword validates successfully when applying its subschema, in @@ -140,12 +138,12 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // evaluated all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword("contains").value(true).build()); } else { executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword("contains").value(indexes).build()); } } @@ -157,22 +155,26 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // Omitted keywords MUST NOT produce annotation results. However, as described // in the section for contains, the absence of this keyword's annotation causes // contains to assume a minimum value of 1. + executionContext.evaluationPathAddLast(minContainsKeyword); executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath.append(minContainsKeyword)) + .evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.schemaLocation.append(minContainsKeyword)) .keyword(minContainsKeyword).value(this.min).build()); + executionContext.evaluationPathRemoveLast(); } } if (this.max != null) { String maxContainsKeyword = "maxContains"; if (collectAnnotations || collectAnnotations(executionContext, maxContainsKeyword)) { + executionContext.evaluationPathAddLast(maxContainsKeyword); executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath.append(maxContainsKeyword)) + .evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.schemaLocation.append(maxContainsKeyword)) .keyword(maxContainsKeyword).value(this.max).build()); + executionContext.evaluationPathRemoveLast(); } } } @@ -181,7 +183,6 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode @Override public void preloadSchema() { Optional.ofNullable(this.schema).ifPresent(Schema::initializeValidators); - collectAnnotations(); // cache the flag } private void boundsViolated(ExecutionContext executionContext, KeywordType validatorTypeCode, Locale locale, @@ -192,26 +193,9 @@ private void boundsViolated(ExecutionContext executionContext, KeywordType valid } else if (KeywordType.MAX_CONTAINS.equals(validatorTypeCode)) { messageKey = CONTAINS_MAX; } - executionContext.addError(error().instanceNode(instanceNode).instanceLocation(instanceLocation).messageKey(messageKey) + executionContext.addError(error().instanceNode(instanceNode).instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()).messageKey(messageKey) .locale(locale).arguments(String.valueOf(bounds), this.schema.getSchemaNode().toString()) .keyword(validatorTypeCode.getValue()).build()); } - /** - * Determine if annotations must be collected for evaluation. - *

- * This will be collected regardless of whether it is needed for reporting. - * - * @return true if annotations must be collected for evaluation. - */ - private boolean collectAnnotations() { - return hasUnevaluatedItemsValidator(); - } - - private boolean hasUnevaluatedItemsValidator() { - if (this.hasUnevaluatedItemsValidator == null) { - this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems"); - } - return hasUnevaluatedItemsValidator; - } } diff --git a/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java b/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java index d384c9fee..758ffc420 100644 --- a/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ContentEncodingValidator.java @@ -40,15 +40,13 @@ public class ContentEncodingValidator extends BaseKeywordValidator { * Constructor. * * @param schemaLocation the schema location - * @param evaluationPath the evaluation path * @param schemaNode the schema node * @param parentSchema the parent schema * @param schemaContext the schema context */ - public ContentEncodingValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public ContentEncodingValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.CONTENT_ENCODING, schemaNode, schemaLocation, parentSchema, schemaContext, - evaluationPath); + super(KeywordType.CONTENT_ENCODING, schemaNode, schemaLocation, parentSchema, schemaContext); this.contentEncoding = schemaNode.textValue(); } @@ -81,7 +79,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (!matches(node.asText())) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.contentEncoding) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java b/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java index f4fe1044a..98e9aae71 100644 --- a/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ContentMediaTypeValidator.java @@ -45,14 +45,13 @@ public class ContentMediaTypeValidator extends BaseKeywordValidator { * Constructor. * * @param schemaLocation the schema location - * @param evaluationPath the evaluation path * @param schemaNode the schema node * @param parentSchema the parent schema * @param schemaContext the schema context */ - public ContentMediaTypeValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public ContentMediaTypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.CONTENT_MEDIA_TYPE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.CONTENT_MEDIA_TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); this.contentMediaType = schemaNode.textValue(); } @@ -104,7 +103,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (!matches(node.asText())) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.contentMediaType) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java b/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java index c3426f70c..af18b6293 100644 --- a/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/DependenciesValidator.java @@ -36,14 +36,13 @@ public class DependenciesValidator extends BaseKeywordValidator implements Keywo * Constructor. * * @param schemaLocation the schema location - * @param evaluationPath the evaluation path * @param schemaNode the schema node * @param parentSchema the parent schema * @param schemaContext the schema context */ - public DependenciesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + public DependenciesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext); for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { String pname = it.next(); @@ -59,7 +58,7 @@ public DependenciesValidator(SchemaLocation schemaLocation, NodePath evaluationP } } else if (pvalue.isObject() || pvalue.isBoolean()) { schemaDeps.put(pname, schemaContext.newSchema(schemaLocation.append(pname), - evaluationPath.append(pname), pvalue, parentSchema)); + pvalue, parentSchema)); } } } @@ -72,14 +71,19 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode for (String field : deps) { if (node.get(field) == null) { executionContext.addError(error().instanceNode(node).property(pname).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(propertyDeps.toString()).build()); } } } Schema schema = schemaDeps.get(pname); if (schema != null) { - schema.validate(executionContext, node, rootNode, instanceLocation); + executionContext.evaluationPathAddLast(pname); + try { + schema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + } } } } diff --git a/src/main/java/com/networknt/schema/keyword/DependentRequired.java b/src/main/java/com/networknt/schema/keyword/DependentRequired.java index d6848c438..88384a368 100644 --- a/src/main/java/com/networknt/schema/keyword/DependentRequired.java +++ b/src/main/java/com/networknt/schema/keyword/DependentRequired.java @@ -31,9 +31,9 @@ public class DependentRequired extends BaseKeywordValidator implements KeywordValidator { private final Map> propertyDependencies = new HashMap<>(); - public DependentRequired(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + public DependentRequired(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.DEPENDENT_REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.DEPENDENT_REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext); for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { String pname = it.next(); @@ -56,7 +56,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode for (String field : dependencies) { if (node.get(field) == null) { executionContext.addError(error().instanceNode(node).property(pname).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(field, pname) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/DependentSchemas.java b/src/main/java/com/networknt/schema/keyword/DependentSchemas.java index fb27538d2..8f87bdc9f 100644 --- a/src/main/java/com/networknt/schema/keyword/DependentSchemas.java +++ b/src/main/java/com/networknt/schema/keyword/DependentSchemas.java @@ -31,16 +31,15 @@ public class DependentSchemas extends BaseKeywordValidator { private final Map schemaDependencies = new HashMap<>(); - public DependentSchemas(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - - super(KeywordType.DEPENDENT_SCHEMAS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); - - for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { + public DependentSchemas(SchemaLocation schemaLocation, JsonNode schemaNode, + Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.DEPENDENT_SCHEMAS, schemaNode, schemaLocation, parentSchema, schemaContext); + for (Iterator it = schemaNode.fieldNames(); it.hasNext();) { String pname = it.next(); JsonNode pvalue = schemaNode.get(pname); if (pvalue.isObject() || pvalue.isBoolean()) { this.schemaDependencies.put(pname, schemaContext.newSchema(schemaLocation.append(pname), - evaluationPath.append(pname), pvalue, parentSchema)); + pvalue, parentSchema)); } } } @@ -57,10 +56,15 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo String pname = it.next(); Schema schema = this.schemaDependencies.get(pname); if (schema != null) { - if(!walk) { - schema.validate(executionContext, node, rootNode, instanceLocation); - } else { - schema.walk(executionContext, node, rootNode, instanceLocation, true); + executionContext.evaluationPathAddLast(pname); + try { + if(!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); } } } diff --git a/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java b/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java index cdce9ac1d..bc85d0011 100644 --- a/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java +++ b/src/main/java/com/networknt/schema/keyword/DiscriminatorValidator.java @@ -63,9 +63,9 @@ public class DiscriminatorValidator extends BaseKeywordValidator { */ private final String defaultMapping; - public DiscriminatorValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public DiscriminatorValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.DISCRIMINATOR, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.DISCRIMINATOR, schemaNode, schemaLocation, parentSchema, schemaContext); ObjectNode discriminator = schemaNode.isObject() ? (ObjectNode) schemaNode : null; if (discriminator != null) { JsonNode propertyName = discriminator.get("propertyName"); @@ -194,7 +194,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode */ if (this.schemaContext.getSchemaRegistryConfig().isStrict("discriminator", Boolean.FALSE)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .messageKey("discriminator.missing_discriminating_value").arguments(this.propertyName).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java b/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java index 35648c6f4..fc6d497dc 100644 --- a/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/DynamicRefValidator.java @@ -21,65 +21,59 @@ import com.networknt.schema.ExecutionContext; import com.networknt.schema.InvalidSchemaRefException; import com.networknt.schema.Schema; -import com.networknt.schema.SchemaException; import com.networknt.schema.SchemaRef; import com.networknt.schema.path.NodePath; import com.networknt.schema.utils.ThreadSafeCachingSupplier; import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaContext; +import java.util.Iterator; import java.util.function.Supplier; /** * {@link KeywordValidator} that resolves $dynamicRef. */ public class DynamicRefValidator extends BaseKeywordValidator { - protected final SchemaRef schema; - public DynamicRefValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.DYNAMIC_REF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); - String refValue = schemaNode.asText(); - this.schema = getRefSchema(parentSchema, schemaContext, refValue, evaluationPath); + public DynamicRefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.DYNAMIC_REF, schemaNode, schemaLocation, parentSchema, schemaContext); } - static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, String refValue, - NodePath evaluationPath) { + static SchemaRef getRefSchema(Schema parentSchema, String refValue, + ExecutionContext executionContext) { String ref = resolve(parentSchema, refValue); return new SchemaRef(getSupplier(() -> { - Schema refSchema = schemaContext.getDynamicAnchors().get(ref); + SchemaContext schemaContext = parentSchema.getSchemaContext(); + Schema refSchema = parentSchema.getSchemaContext().getDynamicAnchors().get(ref); if (refSchema == null) { // This is a $dynamicRef without a matching $dynamicAnchor // A $dynamicRef without a matching $dynamicAnchor in the same schema resource // behaves like a normal $ref to $anchor // A $dynamicRef without anchor in fragment behaves identical to $ref - SchemaRef r = RefValidator.getRefSchema(parentSchema, schemaContext, refValue, evaluationPath); + SchemaRef r = RefValidator.getRefSchema(parentSchema, schemaContext, refValue); if (r != null) { refSchema = r.getSchema(); } } else { // Check parents - Schema base = parentSchema; + Schema base; int index = ref.indexOf("#"); String anchor = ref.substring(index); String absoluteIri = ref.substring(0, index); - while (base.getEvaluationParentSchema() != null) { - base = base.getEvaluationParentSchema(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + base = iter.next(); String baseAbsoluteIri = base.getSchemaLocation().getAbsoluteIri() != null ? base.getSchemaLocation().getAbsoluteIri().toString() : ""; if (!baseAbsoluteIri.equals(absoluteIri)) { absoluteIri = baseAbsoluteIri; String parentRef = SchemaLocation.resolve(base.getSchemaLocation(), anchor); - Schema parentRefSchema = schemaContext.getDynamicAnchors().get(parentRef); + Schema parentRefSchema = base.getSchemaContext().getDynamicAnchors().get(parentRef); if (parentRefSchema != null) { refSchema = parentRefSchema; } } } } - - if (refSchema != null) { - refSchema = refSchema.fromRef(parentSchema, evaluationPath); - } return refSchema; - }, schemaContext.getSchemaRegistryConfig().isCacheRefs())); + }, false)); } static Supplier getSupplier(Supplier supplier, boolean cache) { @@ -97,11 +91,11 @@ private static String resolve(Schema parentSchema, String refValue) { @Override public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { - Schema refSchema = this.schema.getSchema(); + Schema refSchema = getSchemaRef(executionContext).getSchema(); if (refSchema == null) { Error error = error().keyword(KeywordType.DYNAMIC_REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } @@ -113,21 +107,20 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, // these schemas will be cached along with config. We have to replace the config for cached $ref references // with the latest config. Reset the config. - Schema refSchema = this.schema.getSchema(); + Schema refSchema = getSchemaRef(executionContext).getSchema(); if (refSchema == null) { Error error = error().keyword(KeywordType.DYNAMIC_REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } if (node == null) { // Check for circular dependency - SchemaLocation schemaLocation = refSchema.getSchemaLocation(); - Schema check = refSchema; boolean circularDependency = false; - while (check.getEvaluationParentSchema() != null) { - check = check.getEvaluationParentSchema(); + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); if (check.getSchemaLocation().equals(schemaLocation)) { circularDependency = true; break; @@ -140,39 +133,8 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); } - public SchemaRef getSchemaRef() { - return this.schema; + public SchemaRef getSchemaRef(ExecutionContext executionContext ) { + String refValue = schemaNode.asText(); + return getRefSchema(this.getParentSchema(), refValue, executionContext); } - - @Override - public void preloadSchema() { - Schema jsonSchema = null; - try { - jsonSchema = this.schema.getSchema(); - } catch (SchemaException e) { - throw e; - } catch (RuntimeException e) { - throw new SchemaException(e); - } - // Check for circular dependency - // Only one cycle is pre-loaded - // The rest of the cycles will load at execution time depending on the input - // data - SchemaLocation schemaLocation = jsonSchema.getSchemaLocation(); - Schema check = jsonSchema; - boolean circularDependency = false; - int depth = 0; - while (check.getEvaluationParentSchema() != null) { - depth++; - check = check.getEvaluationParentSchema(); - if (check.getSchemaLocation().equals(schemaLocation)) { - circularDependency = true; - break; - } - } - if (this.schemaContext.getSchemaRegistryConfig().isCacheRefs() && !circularDependency - && depth < this.schemaContext.getSchemaRegistryConfig().getPreloadSchemaRefMaxNestingDepth()) { - jsonSchema.initializeValidators(); - } - } } diff --git a/src/main/java/com/networknt/schema/keyword/EnumValidator.java b/src/main/java/com/networknt/schema/keyword/EnumValidator.java index 6ad8d9d50..8ee06e794 100644 --- a/src/main/java/com/networknt/schema/keyword/EnumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/EnumValidator.java @@ -48,8 +48,8 @@ static String asText(JsonNode node) { return node.asText(); } - public EnumValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ENUM, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public EnumValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ENUM, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode != null && schemaNode.isArray()) { nodes = new HashSet<>(); StringBuilder sb = new StringBuilder(); @@ -100,7 +100,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } if (!nodes.contains(node) && !( this.schemaContext.getSchemaRegistryConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(error).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java index bc05fd9ae..d7aa9f5d7 100644 --- a/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ExclusiveMaximumValidator.java @@ -35,8 +35,8 @@ public class ExclusiveMaximumValidator extends BaseKeywordValidator { private final ThresholdMixin typedMaximum; - public ExclusiveMaximumValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.EXCLUSIVE_MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public ExclusiveMaximumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.EXCLUSIVE_MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isNumber()) { throw new SchemaException("exclusiveMaximum value is not a number"); } @@ -105,7 +105,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (typedMaximum.crossesThreshold(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(typedMaximum.thresholdValue()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java index 297259121..200965fd7 100644 --- a/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ExclusiveMinimumValidator.java @@ -39,8 +39,8 @@ public class ExclusiveMinimumValidator extends BaseKeywordValidator { */ private final ThresholdMixin typedMinimum; - public ExclusiveMinimumValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.EXCLUSIVE_MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public ExclusiveMinimumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.EXCLUSIVE_MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isNumber()) { throw new SchemaException("exclusiveMinimum value is not a number"); } @@ -112,7 +112,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (typedMinimum.crossesThreshold(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(typedMinimum.thresholdValue()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/FalseValidator.java b/src/main/java/com/networknt/schema/keyword/FalseValidator.java index dd1aaf578..49025e2c1 100644 --- a/src/main/java/com/networknt/schema/keyword/FalseValidator.java +++ b/src/main/java/com/networknt/schema/keyword/FalseValidator.java @@ -18,25 +18,24 @@ import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.Schema; +import com.networknt.schema.SchemaContext; import com.networknt.schema.SchemaLocation; import com.networknt.schema.path.NodePath; -import com.networknt.schema.SchemaContext; /** * {@link KeywordValidator} for false. */ public class FalseValidator extends BaseKeywordValidator implements KeywordValidator { - private final String reason; - public FalseValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.FALSE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); - this.reason = this.evaluationPath.getParent().getName(-1); + public FalseValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.FALSE, schemaNode, schemaLocation, parentSchema, schemaContext); } public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { // For the false validator, it is always not valid + String reason = executionContext.getEvaluationPath().getParent().getName(-1); executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(reason).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/FormatKeyword.java b/src/main/java/com/networknt/schema/keyword/FormatKeyword.java index 1c21e4f13..6e35bc112 100644 --- a/src/main/java/com/networknt/schema/keyword/FormatKeyword.java +++ b/src/main/java/com/networknt/schema/keyword/FormatKeyword.java @@ -20,7 +20,6 @@ import com.networknt.schema.Schema; import com.networknt.schema.SchemaLocation; import com.networknt.schema.format.Format; -import com.networknt.schema.path.NodePath; import com.networknt.schema.SchemaContext; import java.util.Collection; @@ -52,13 +51,13 @@ Collection getFormats() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { Format format = null; if (schemaNode != null && schemaNode.isTextual()) { String formatName = schemaNode.textValue(); format = this.formats.get(formatName); } - return new FormatValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, schemaContext, format, + return new FormatValidator(schemaLocation, schemaNode, parentSchema, schemaContext, format, this); } diff --git a/src/main/java/com/networknt/schema/keyword/FormatValidator.java b/src/main/java/com/networknt/schema/keyword/FormatValidator.java index aa26df6d4..f9a6c5814 100644 --- a/src/main/java/com/networknt/schema/keyword/FormatValidator.java +++ b/src/main/java/com/networknt/schema/keyword/FormatValidator.java @@ -38,10 +38,10 @@ public class FormatValidator extends BaseFormatValidator implements KeywordValid private final Format format; - public FormatValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public FormatValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext, Format format, Keyword keyword) { - super(schemaLocation, evaluationPath, schemaNode, parentSchema, keyword, schemaContext); + super(schemaLocation, schemaNode, parentSchema, keyword, schemaContext); this.format = format; } @@ -76,7 +76,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode format.validate(executionContext, schemaContext, node, rootNode, instanceLocation, assertionsEnabled, () -> this.error().instanceNode(node).instanceLocation(instanceLocation) - .messageKey(format.getMessageKey()) + .evaluationPath(executionContext.getEvaluationPath()).messageKey(format.getMessageKey()) .locale(executionContext.getExecutionConfig().getLocale()) , this); @@ -106,7 +106,7 @@ protected void validateUnknownFormat(ExecutionContext executionContext, */ if (createUnknownFormatAssertions(executionContext) && this.schemaNode.isTextual()) { executionContext.addError(error().instanceLocation(instanceLocation).instanceNode(node) - .messageKey("format.unknown").arguments(schemaNode.textValue()).build()); + .evaluationPath(executionContext.getEvaluationPath()).messageKey("format.unknown").arguments(schemaNode.textValue()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/IfValidator.java b/src/main/java/com/networknt/schema/keyword/IfValidator.java index 65923bdc6..22e5053be 100644 --- a/src/main/java/com/networknt/schema/keyword/IfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/IfValidator.java @@ -36,8 +36,8 @@ public class IfValidator extends BaseKeywordValidator { private final Schema thenSchema; private final Schema elseSchema; - public IfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.IF_THEN_ELSE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public IfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.IF_THEN_ELSE, schemaNode, schemaLocation, parentSchema, schemaContext); Schema foundIfSchema = null; Schema foundThenSchema = null; @@ -46,15 +46,14 @@ public IfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonN for (final String keyword : KEYWORDS) { final JsonNode node = parentSchema.getSchemaNode().get(keyword); final SchemaLocation schemaLocationOfSchema = parentSchema.getSchemaLocation().append(keyword); - final NodePath evaluationPathOfSchema = parentSchema.getEvaluationPath().append(keyword); if (keyword.equals("if")) { - foundIfSchema = schemaContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node, + foundIfSchema = schemaContext.newSchema(schemaLocationOfSchema, node, parentSchema); } else if (keyword.equals("then") && node != null) { - foundThenSchema = schemaContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node, + foundThenSchema = schemaContext.newSchema(schemaLocationOfSchema, node, parentSchema); } else if (keyword.equals("else") && node != null) { - foundElseSchema = schemaContext.newSchema(schemaLocationOfSchema, evaluationPathOfSchema, node, + foundElseSchema = schemaContext.newSchema(schemaLocationOfSchema, node, parentSchema); } } @@ -66,8 +65,6 @@ public IfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonN @Override public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { - - boolean ifConditionPassed = false; // Save flag as nested schema evaluation shouldn't trigger fail fast @@ -86,9 +83,25 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } if (ifConditionPassed && this.thenSchema != null) { - this.thenSchema.validate(executionContext, node, rootNode, instanceLocation); + // The "if" keyword is a bit unusual as it actually handles multiple keywords + // This removes the "if" in the evaluation path so the rest of the evaluation paths will be correct + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } } else if (!ifConditionPassed && this.elseSchema != null) { - this.elseSchema.validate(executionContext, node, rootNode, instanceLocation); + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } } } @@ -107,6 +120,9 @@ public void preloadSchema() { @Override public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { + // The "if" keyword is a bit unusual as it actually handles multiple keywords + // This removes the "if" in the evaluation path so the rest of the evaluation paths will be correct + boolean checkCondition = node != null && shouldValidateSchema; boolean ifConditionPassed = false; @@ -126,17 +142,44 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } if (!checkCondition) { if (this.thenSchema != null) { - this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } } if (this.elseSchema != null) { - this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } } } else { if (this.thenSchema != null && ifConditionPassed) { - this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); - } - else if (this.elseSchema != null && !ifConditionPassed) { - this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("then"); + try { + this.thenSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } + } else if (this.elseSchema != null && !ifConditionPassed) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("else"); + try { + this.elseSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("if"); + } } } } diff --git a/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java b/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java index 92ead9533..85e5c7d7e 100644 --- a/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ItemsLegacyValidator.java @@ -16,19 +16,21 @@ package com.networknt.schema.keyword; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.Schema; -import com.networknt.schema.SchemaRef; -import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaContext; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRef; import com.networknt.schema.annotation.Annotation; import com.networknt.schema.path.NodePath; import com.networknt.schema.utils.SchemaRefs; -import java.util.*; - /** * {@link KeywordValidator} for items Draft 4 to Draft 2019-09. */ @@ -40,14 +42,11 @@ public class ItemsLegacyValidator extends BaseKeywordValidator { private final Boolean additionalItems; private final Schema additionalSchema; - private Boolean hasUnevaluatedItemsValidator = null; - - private final NodePath additionalItemsEvaluationPath; private final SchemaLocation additionalItemsSchemaLocation; private final JsonNode additionalItemsSchemaNode; - public ItemsLegacyValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ITEMS_LEGACY, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public ItemsLegacyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.ITEMS_LEGACY, schemaNode, schemaLocation, parentSchema, schemaContext); Boolean additionalItems = null; @@ -56,13 +55,13 @@ public ItemsLegacyValidator(SchemaLocation schemaLocation, NodePath evaluationPa JsonNode additionalItemsSchemaNode = null; if (schemaNode.isObject() || schemaNode.isBoolean()) { - foundSchema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + foundSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); this.tupleSchema = Collections.emptyList(); } else { int i = 0; this.tupleSchema = new ArrayList<>(schemaNode.size()); for (JsonNode s : schemaNode) { - this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), + this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), s, parentSchema)); i++; } @@ -75,14 +74,13 @@ public ItemsLegacyValidator(SchemaLocation schemaLocation, NodePath evaluationPa } else if (addItemNode.isObject()) { foundAdditionalSchema = schemaContext.newSchema( parentSchema.getSchemaLocation().append(PROPERTY_ADDITIONAL_ITEMS), - parentSchema.getEvaluationPath().append(PROPERTY_ADDITIONAL_ITEMS), addItemNode, parentSchema); + addItemNode, parentSchema); } } } this.additionalItems = additionalItems; this.schema = foundSchema; this.additionalSchema = foundAdditionalSchema; - this.additionalItemsEvaluationPath = parentSchema.getEvaluationPath().append(PROPERTY_ADDITIONAL_ITEMS); this.additionalItemsSchemaLocation = parentSchema.getSchemaLocation().append(PROPERTY_ADDITIONAL_ITEMS); this.additionalItemsSchemaNode = additionalItemsSchemaNode; } @@ -95,7 +93,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // ignores non-arrays return; } - boolean collectAnnotations = collectAnnotations(); + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); // Add items annotation if (collectAnnotations || collectAnnotations(executionContext)) { @@ -103,7 +101,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } else if (this.tupleSchema != null) { // Tuples @@ -113,13 +111,13 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // More items than schemas so the keyword only applied to the number of schemas executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(schemas).build()); } else { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -142,11 +140,13 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (hasAdditionalItem) { if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) { + executionContext.evaluationPathAddLast("additionalItems"); executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.additionalItemsEvaluationPath) + .evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.additionalItemsSchemaLocation) .keyword("additionalItems").value(true).build()); + executionContext.evaluationPathRemoveLast(); } } } @@ -163,7 +163,13 @@ private boolean doValidate(ExecutionContext executionContext, int i, JsonNode no } else if (this.tupleSchema != null) { if (i < this.tupleSchema.size()) { // validate against tuple schema - this.tupleSchema.get(i).validate(executionContext, node, rootNode, path); + executionContext.evaluationPathAddLast(i); + try { + this.tupleSchema.get(i).validate(executionContext, node, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } else { if ((this.additionalItems != null && this.additionalItems) || this.additionalSchema != null) { isAdditionalItem = true; @@ -171,21 +177,35 @@ private boolean doValidate(ExecutionContext executionContext, int i, JsonNode no if (this.additionalSchema != null) { // validate against additional item schema - this.additionalSchema.validate(executionContext, node, rootNode, path); + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast("additionalItems"); + try { + this.additionalSchema.validate(executionContext, node, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } } else if (this.additionalItems != null) { if (this.additionalItems) { // evaluatedItems.add(path); } else { // no additional item allowed, return error - executionContext.addError(error().instanceNode(rootNode).instanceLocation(instanceLocation) - .keyword("additionalItems") - .messageKey("additionalItems") - .evaluationPath(this.additionalItemsEvaluationPath) - .schemaLocation(this.additionalItemsSchemaLocation) - .schemaNode(this.additionalItemsSchemaNode) - .locale(executionContext.getExecutionConfig().getLocale()) - .index(i) - .arguments(i).build()); + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast("additionalItems"); + try { + executionContext.addError(error().instanceNode(rootNode).instanceLocation(instanceLocation) + .keyword("additionalItems") + .messageKey("additionalItems") + .evaluationPath(executionContext.getEvaluationPath()) + .schemaLocation(this.additionalItemsSchemaLocation) + .schemaNode(this.additionalItemsSchemaNode) + .locale(executionContext.getExecutionConfig().getLocale()) + .index(i) + .arguments(i).build()); + } finally { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } } } } @@ -195,7 +215,7 @@ private boolean doValidate(ExecutionContext executionContext, int i, JsonNode no @Override public void walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { - boolean collectAnnotations = collectAnnotations(); + boolean collectAnnotations = hasUnevaluatedItemsInEvaluationPath(executionContext); // Add items annotation if (collectAnnotations || collectAnnotations(executionContext)) { @@ -203,7 +223,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } else if (this.tupleSchema != null) { // Tuples @@ -213,13 +233,13 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // More items than schemas so the keyword only applied to the number of schemas executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(schemas).build()); } else { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -232,7 +252,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root ArrayNode arrayNode = (ArrayNode) node; JsonNode defaultNode = null; if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { - defaultNode = getDefaultNode(this.schema); + defaultNode = getDefaultNode(this.schema, executionContext); } for (int i = 0; i < count; i++) { JsonNode n = arrayNode.get(i); @@ -257,7 +277,7 @@ else if (this.tupleSchema != null) { JsonNode defaultNode = null; JsonNode n = arrayNode.get(i); if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { - defaultNode = getDefaultNode(this.tupleSchema.get(i)); + defaultNode = getDefaultNode(this.tupleSchema.get(i), executionContext); } if (n != null) { if (n.isNull() && defaultNode != null) { @@ -265,11 +285,21 @@ else if (this.tupleSchema != null) { n = defaultNode; } } - walkSchema(executionContext, this.tupleSchema.get(i), n, rootNode, instanceLocation.append(i), - shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + executionContext.evaluationPathAddLast(i); + try { + walkSchema(executionContext, this.tupleSchema.get(i), n, rootNode, instanceLocation.append(i), + shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } finally { + executionContext.evaluationPathRemoveLast(); + } } else { - walkSchema(executionContext, this.tupleSchema.get(i), null, rootNode, instanceLocation.append(i), - shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + executionContext.evaluationPathAddLast(i); + try { + walkSchema(executionContext, this.tupleSchema.get(i), null, rootNode, + instanceLocation.append(i), shouldValidateSchema, KeywordType.ITEMS_LEGACY.getValue()); + } finally { + executionContext.evaluationPathRemoveLast(); + } } } if (this.additionalSchema != null) { @@ -284,7 +314,7 @@ else if (this.tupleSchema != null) { JsonNode defaultNode = null; JsonNode n = arrayNode.get(i); if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { - defaultNode = getDefaultNode(this.additionalSchema); + defaultNode = getDefaultNode(this.additionalSchema, executionContext); } if (n != null) { if (n.isNull() && defaultNode != null) { @@ -305,23 +335,25 @@ else if (this.tupleSchema != null) { if (hasAdditionalItem) { if (collectAnnotations || collectAnnotations(executionContext, "additionalItems")) { + executionContext.evaluationPathAddLast("additionalItems"); executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.additionalItemsEvaluationPath) + .evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.additionalItemsSchemaLocation) .keyword("additionalItems").value(true).build()); + executionContext.evaluationPathRemoveLast(); } } } } } - private static JsonNode getDefaultNode(Schema schema) { + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { JsonNode result = schema.getSchemaNode().get("default"); if (result == null) { - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); if (schemaRef != null) { - result = getDefaultNode(schemaRef.getSchema()); + result = getDefaultNode(schemaRef.getSchema(), executionContext); } } return result; @@ -329,15 +361,27 @@ private static JsonNode getDefaultNode(Schema schema) { private void walkSchema(ExecutionContext executionContext, Schema walkSchema, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema, String keyword) { - boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner().runPreWalkListeners(executionContext, keyword, - node, rootNode, instanceLocation, walkSchema, this); - int currentErrors = executionContext.getErrors().size(); - if (executeWalk) { - walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + boolean additionalItems = "additionalItems".equals(keyword); + if (additionalItems) { + executionContext.evaluationPathRemoveLast(); // remove items + executionContext.evaluationPathAddLast(keyword); + } + try { + boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner() + .runPreWalkListeners(executionContext, keyword, node, rootNode, instanceLocation, walkSchema, this); + int currentErrors = executionContext.getErrors().size(); + if (executeWalk) { + walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } + executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, keyword, + node, rootNode, instanceLocation, walkSchema, this, + executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); + } finally { + if (additionalItems) { + executionContext.evaluationPathRemoveLast(); + executionContext.evaluationPathAddLast("items"); + } } - executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners(executionContext, keyword, node, rootNode, - instanceLocation, walkSchema, this, executionContext.getErrors().subList(currentErrors, executionContext.getErrors().size())); - } public List getTupleSchema() { @@ -348,17 +392,6 @@ public Schema getSchema() { return this.schema; } - private boolean collectAnnotations() { - return hasUnevaluatedItemsValidator(); - } - - private boolean hasUnevaluatedItemsValidator() { - if (this.hasUnevaluatedItemsValidator == null) { - this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems"); - } - return hasUnevaluatedItemsValidator; - } - @Override public void preloadSchema() { if (null != this.schema) { @@ -368,6 +401,5 @@ public void preloadSchema() { if (null != this.additionalSchema) { this.additionalSchema.initializeValidators(); } - collectAnnotations(); // cache the flag } -} +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/keyword/ItemsValidator.java b/src/main/java/com/networknt/schema/keyword/ItemsValidator.java index 644f2e62d..5877edaf3 100644 --- a/src/main/java/com/networknt/schema/keyword/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ItemsValidator.java @@ -35,12 +35,9 @@ public class ItemsValidator extends BaseKeywordValidator { private final int prefixCount; private final boolean additionalItems; - private Boolean hasUnevaluatedItemsValidator = null; - - public ItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public ItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, - evaluationPath); + super(KeywordType.ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); JsonNode prefixItems = parentSchema.getSchemaNode().get("prefixItems"); if (prefixItems instanceof ArrayNode) { @@ -52,7 +49,7 @@ public ItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, Js } if (schemaNode.isObject() || schemaNode.isBoolean()) { - this.schema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } else { throw new IllegalArgumentException("The value of 'items' MUST be a valid JSON Schema."); } @@ -78,17 +75,17 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // generate a helpful message int x = i; executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .index(x).arguments(x).build()); } evaluated = true; } if (evaluated) { - if (collectAnnotations() || collectAnnotations(executionContext)) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -103,7 +100,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root JsonNode defaultNode = null; if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults() && this.schema != null) { - defaultNode = getDefaultNode(this.schema); + defaultNode = getDefaultNode(this.schema, executionContext); } boolean evaluated = false; for (int i = this.prefixCount; i < node.size(); ++i) { @@ -119,11 +116,11 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } } if (evaluated) { - if (collectAnnotations() || collectAnnotations(executionContext)) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -135,12 +132,12 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } } - private static JsonNode getDefaultNode(Schema schema) { + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { JsonNode result = schema.getSchemaNode().get("default"); if (result == null) { - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); if (schemaRef != null) { - result = getDefaultNode(schemaRef.getSchema()); + result = getDefaultNode(schemaRef.getSchema(), executionContext); } } return result; @@ -151,7 +148,7 @@ private void walkSchema(ExecutionContext executionContext, Schema walkSchema, Js //@formatter:off boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner().runPreWalkListeners( executionContext, - KeywordType.ITEMS_LEGACY.getValue(), + KeywordType.ITEMS.getValue(), node, rootNode, instanceLocation, @@ -163,7 +160,7 @@ private void walkSchema(ExecutionContext executionContext, Schema walkSchema, Js } executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners( executionContext, - KeywordType.ITEMS_LEGACY.getValue(), + KeywordType.ITEMS.getValue(), node, rootNode, instanceLocation, @@ -180,18 +177,5 @@ public Schema getSchema() { @Override public void preloadSchema() { this.schema.initializeValidators(); - collectAnnotations(); // cache the flag - } - - private boolean collectAnnotations() { - return hasUnevaluatedItemsValidator(); } - - private boolean hasUnevaluatedItemsValidator() { - if (this.hasUnevaluatedItemsValidator == null) { - this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems"); - } - return hasUnevaluatedItemsValidator; - } - } diff --git a/src/main/java/com/networknt/schema/keyword/Keyword.java b/src/main/java/com/networknt/schema/keyword/Keyword.java index 93a01b666..596c7ea62 100644 --- a/src/main/java/com/networknt/schema/keyword/Keyword.java +++ b/src/main/java/com/networknt/schema/keyword/Keyword.java @@ -20,7 +20,6 @@ import com.networknt.schema.Schema; import com.networknt.schema.SchemaException; import com.networknt.schema.SchemaLocation; -import com.networknt.schema.path.NodePath; import com.networknt.schema.SchemaContext; /** @@ -38,7 +37,6 @@ public interface Keyword { * Creates a new validator for the keyword. * * @param schemaLocation the schema location - * @param evaluationPath the evaluation path * @param schemaNode the schema node * @param parentSchema the parent schema * @param schemaContext the schema context @@ -46,6 +44,6 @@ public interface Keyword { * @throws SchemaException the exception * @throws Exception the exception */ - KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception; } diff --git a/src/main/java/com/networknt/schema/keyword/KeywordType.java b/src/main/java/com/networknt/schema/keyword/KeywordType.java index d89e4b1ca..a199cde1b 100644 --- a/src/main/java/com/networknt/schema/keyword/KeywordType.java +++ b/src/main/java/com/networknt/schema/keyword/KeywordType.java @@ -21,7 +21,6 @@ import com.networknt.schema.SchemaLocation; import com.networknt.schema.SpecificationVersion; import com.networknt.schema.SpecificationVersionRange; -import com.networknt.schema.path.NodePath; import com.networknt.schema.SchemaContext; import java.util.ArrayList; @@ -31,7 +30,7 @@ @FunctionalInterface interface ValidatorFactory { - KeywordValidator newInstance(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + KeywordValidator newInstance(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext); } @@ -53,7 +52,7 @@ public enum KeywordType implements Keyword { EXCLUSIVE_MINIMUM("exclusiveMinimum", ExclusiveMinimumValidator::new, SpecificationVersionRange.DRAFT_6_TO_DRAFT_7), FALSE("false", FalseValidator::new, SpecificationVersionRange.MIN_DRAFT_6), FORMAT("format", null, SpecificationVersionRange.MAX_DRAFT_7) { - @Override public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + @Override public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { throw new UnsupportedOperationException("Use FormatKeyword instead"); } }, @@ -131,12 +130,12 @@ public static KeywordType fromValue(String value) { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { if (this.validatorFactory == null) { throw new UnsupportedOperationException("No suitable validator for " + getValue()); } - return validatorFactory.newInstance(schemaLocation, evaluationPath, schemaNode, parentSchema, + return validatorFactory.newInstance(schemaLocation, schemaNode, parentSchema, schemaContext); } diff --git a/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java b/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java index 11b405213..049101155 100644 --- a/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MaxItemsValidator.java @@ -29,8 +29,8 @@ public class MaxItemsValidator extends BaseKeywordValidator implements KeywordValidator { private final int max; - public MaxItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MAX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MaxItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.canConvertToExactIntegral()) { this.max = schemaNode.intValue(); } else { @@ -44,13 +44,13 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (node.isArray()) { if (node.size() > this.max) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.max, node.size()).build()); } } else if (this.schemaContext.getSchemaRegistryConfig().isTypeLoose()) { if (1 > this.max) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.max, 1).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java b/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java index bb6312092..0e1cda817 100644 --- a/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MaxLengthValidator.java @@ -31,8 +31,8 @@ public class MaxLengthValidator extends BaseKeywordValidator implements KeywordValidator { private final int maxLength; - public MaxLengthValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MAX_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MaxLengthValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAX_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { this.maxLength = schemaNode.intValue(); } else { @@ -50,7 +50,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } if (node.textValue().codePointCount(0, node.textValue().length()) > this.maxLength) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.maxLength).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java index 20719d936..edef1cd95 100644 --- a/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MaxPropertiesValidator.java @@ -29,9 +29,9 @@ public class MaxPropertiesValidator extends BaseKeywordValidator implements KeywordValidator { private final int max; - public MaxPropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, + public MaxPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MAX_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.MAX_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.canConvertToExactIntegral()) { max = schemaNode.intValue(); } else { @@ -45,7 +45,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (node.isObject()) { if (node.size() > max) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(max).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MaximumValidator.java b/src/main/java/com/networknt/schema/keyword/MaximumValidator.java index c7d6e3a78..8b5db4096 100644 --- a/src/main/java/com/networknt/schema/keyword/MaximumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MaximumValidator.java @@ -40,8 +40,8 @@ public class MaximumValidator extends BaseKeywordValidator { private final ThresholdMixin typedMaximum; - public MaximumValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MaximumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MAXIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isNumber()) { throw new SchemaException("maximum value is not a number"); } @@ -120,7 +120,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (this.typedMaximum.crossesThreshold(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.typedMaximum.thresholdValue()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java b/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java index 48181b1a5..64a1b8aae 100644 --- a/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MinItemsValidator.java @@ -29,8 +29,8 @@ public class MinItemsValidator extends BaseKeywordValidator implements KeywordValidator { private int min = 0; - public MinItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MIN_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MinItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MIN_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.canConvertToExactIntegral()) { min = schemaNode.intValue(); } @@ -42,14 +42,14 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (node.isArray()) { if (node.size() < min) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(min, node.size()) .build()); } } else if (this.schemaContext.getSchemaRegistryConfig().isTypeLoose()) { if (1 < min) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(min, 1).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java b/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java index 456367bb9..c19f70709 100644 --- a/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MinLengthValidator.java @@ -31,8 +31,8 @@ public class MinLengthValidator extends BaseKeywordValidator implements KeywordValidator { private int minLength; - public MinLengthValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MIN_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MinLengthValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MIN_LENGTH, schemaNode, schemaLocation, parentSchema, schemaContext); minLength = Integer.MIN_VALUE; if (schemaNode != null && schemaNode.canConvertToExactIntegral()) { minLength = schemaNode.intValue(); @@ -50,7 +50,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (node.textValue().codePointCount(0, node.textValue().length()) < minLength) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(minLength).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java b/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java index 68d0b3564..2ba469004 100644 --- a/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MinMaxContainsValidator.java @@ -20,9 +20,9 @@ public class MinMaxContainsValidator extends BaseKeywordValidator { private final Set analysis; - public MinMaxContainsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, + public MinMaxContainsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MAX_CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.MAX_CONTAINS, schemaNode, schemaLocation, parentSchema, schemaContext); Set analysis = null; int min = 1; @@ -68,6 +68,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode this.analysis.stream() .map(analysis -> error().instanceNode(node) .instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) .messageKey(analysis.getMessageKey()).locale(executionContext.getExecutionConfig().getLocale()) .keyword(analysis.getMessageKey()) .arguments(parentSchema.getSchemaNode().toString()).build()) diff --git a/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java index bf6142746..69fea4e8a 100644 --- a/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MinPropertiesValidator.java @@ -29,9 +29,9 @@ public class MinPropertiesValidator extends BaseKeywordValidator implements KeywordValidator { protected final int min; - public MinPropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, + public MinPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MIN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.MIN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.canConvertToExactIntegral()) { min = schemaNode.intValue(); } else { @@ -45,7 +45,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (node.isObject()) { if (node.size() < min) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(min).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MinimumValidator.java b/src/main/java/com/networknt/schema/keyword/MinimumValidator.java index 2d9f7f3cd..627b6895c 100644 --- a/src/main/java/com/networknt/schema/keyword/MinimumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MinimumValidator.java @@ -43,8 +43,8 @@ public class MinimumValidator extends BaseKeywordValidator { */ private final ThresholdMixin typedMinimum; - public MinimumValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public MinimumValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.MINIMUM, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isNumber()) { throw new SchemaException("minimum value is not a number"); @@ -127,7 +127,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (this.typedMinimum.crossesThreshold(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.typedMinimum.thresholdValue()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java index a69c41121..a2326601c 100644 --- a/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java @@ -32,9 +32,9 @@ public class MultipleOfValidator extends BaseKeywordValidator implements KeywordValidator { private final BigDecimal divisor; - public MultipleOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public MultipleOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.MULTIPLE_OF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.MULTIPLE_OF, schemaNode, schemaLocation, parentSchema, schemaContext); this.divisor = getDivisor(schemaNode); } @@ -46,7 +46,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (dividend != null) { if (dividend.divideAndRemainder(this.divisor)[1].abs().compareTo(BigDecimal.ZERO) > 0) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.divisor) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java b/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java index 83c857e0c..e49236084 100644 --- a/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java +++ b/src/main/java/com/networknt/schema/keyword/NonValidationKeyword.java @@ -32,21 +32,21 @@ public class NonValidationKeyword extends AbstractKeyword { private static final class Validator extends AbstractKeywordValidator { - public Validator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public Validator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext, Keyword keyword) { - super(keyword, schemaNode, schemaLocation, evaluationPath); + super(keyword, schemaNode, schemaLocation); String id = schemaContext.resolveSchemaId(schemaNode); String anchor = schemaContext.getDialect().readAnchor(schemaNode); String dynamicAnchor = schemaContext.getDialect().readDynamicAnchor(schemaNode); if (id != null || anchor != null || dynamicAnchor != null) { // Used to register schema resources with $id - schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } if ("$defs".equals(keyword.getValue()) || "definitions".equals(keyword.getValue())) { for (Iterator> field = schemaNode.fields(); field.hasNext(); ) { Entry property = field.next(); SchemaLocation location = schemaLocation.append(property.getKey()); - Schema schema = schemaContext.newSchema(location, evaluationPath.append(property.getKey()), + Schema schema = schemaContext.newSchema(location, property.getValue(), parentSchema); schemaContext.getSchemaReferences().put(location.toString(), schema); } @@ -64,8 +64,8 @@ public NonValidationKeyword(String keyword) { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - return new Validator(schemaLocation, evaluationPath, schemaNode, parentSchema, schemaContext, this); + return new Validator(schemaLocation, schemaNode, parentSchema, schemaContext, this); } } diff --git a/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java b/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java index 332650609..7f09b7382 100644 --- a/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java +++ b/src/main/java/com/networknt/schema/keyword/NotAllowedValidator.java @@ -31,8 +31,8 @@ public class NotAllowedValidator extends BaseKeywordValidator implements KeywordValidator { private final List fieldNames; - public NotAllowedValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.NOT_ALLOWED, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public NotAllowedValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.NOT_ALLOWED, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.isArray()) { int size = schemaNode.size(); this.fieldNames = new ArrayList<>(size); @@ -52,7 +52,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (propertyNode != null) { executionContext.addError(error().property(fieldName).instanceNode(node) - .instanceLocation(instanceLocation.append(fieldName)) + .evaluationPath(executionContext.getEvaluationPath()).instanceLocation(instanceLocation.append(fieldName)) .locale(executionContext.getExecutionConfig().getLocale()) .arguments(fieldName).build()); } diff --git a/src/main/java/com/networknt/schema/keyword/NotValidator.java b/src/main/java/com/networknt/schema/keyword/NotValidator.java index 36b53c760..a183c7636 100644 --- a/src/main/java/com/networknt/schema/keyword/NotValidator.java +++ b/src/main/java/com/networknt/schema/keyword/NotValidator.java @@ -32,9 +32,9 @@ public class NotValidator extends BaseKeywordValidator { private final Schema schema; - public NotValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.NOT, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); - this.schema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + public NotValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.NOT, schemaNode, schemaLocation, parentSchema, schemaContext); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } @Override @@ -67,7 +67,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo } if (test.isEmpty()) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.schemaNode.toString()) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/OneOfValidator.java b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java index eee38b822..4358daadf 100644 --- a/src/main/java/com/networknt/schema/keyword/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/OneOfValidator.java @@ -36,11 +36,9 @@ public class OneOfValidator extends BaseKeywordValidator { private final List schemas; - private Boolean canShortCircuit = null; - - public OneOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public OneOfValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.ONE_OF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.ONE_OF, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isArray()) { JsonType nodeType = TypeFactory.getValueNodeType(schemaNode, this.schemaContext.getSchemaRegistryConfig()); throw new SchemaException(error().instanceNode(schemaNode).instanceLocation(schemaLocation.getFragment()) @@ -50,7 +48,7 @@ public OneOfValidator(SchemaLocation schemaLocation, NodePath evaluationPath, Js this.schemas = new ArrayList<>(size); for (int i = 0; i < size; i++) { JsonNode childNode = schemaNode.get(i); - this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), childNode, + this.schemas.add(schemaContext.newSchema(schemaLocation.append(i), childNode, parentSchema)); } } @@ -75,15 +73,23 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo executionContext.setErrors(subSchemaErrors); // Save flag as nested schema evaluation shouldn't trigger fail fast boolean failFast = executionContext.isFailFast(); + Boolean canShortCircuit = null; + int schemaIndex = 0; try { executionContext.setFailFast(false); for (Schema schema : this.schemas) { subSchemaErrors.clear(); - if (!walk) { - schema.validate(executionContext, node, rootNode, instanceLocation); - } else { - schema.walk(executionContext, node, rootNode, instanceLocation, true); + executionContext.evaluationPathAddLast(schemaIndex); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); } + schemaIndex++; // check if any validation errors have occurred if (subSchemaErrors.isEmpty()) { // No new errors @@ -94,11 +100,16 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo indexes.add(Integer.toString(index)); } - if (numberOfValidSchema > 1 && canShortCircuit()) { - // short-circuit - // note that the short circuit means that only 2 valid schemas are reported even - // if could be more - break; + if (numberOfValidSchema > 1) { + if (canShortCircuit == null) { + canShortCircuit = canShortCircuit(executionContext); + } + if (canShortCircuit) { + // short-circuit + // note that the short circuit means that only 2 valid schemas are reported even + // if could be more + break; + } } if (this.schemaContext.isDiscriminatorKeywordEnabled()) { @@ -168,7 +179,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo // if the discriminatingValue is not set in the payload existingErrors .add(error().keyword("discriminator").instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .messageKey("discriminator.oneOf.no_match_found") .arguments(state.getDiscriminatingValue()).build()); } @@ -185,7 +196,7 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo */ Error message = error().instanceNode(node).instanceLocation(instanceLocation) .messageKey(numberOfValidSchema > 1 ? "oneOf.indexes" : "oneOf") - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(Integer.toString(numberOfValidSchema), numberOfValidSchema > 1 ? String.join(", ", indexes) : "") .build(); @@ -213,18 +224,14 @@ protected boolean reportChildErrors(ExecutionContext executionContext) { return !executionContext.getExecutionConfig().isFailFast(); } - protected boolean canShortCircuit() { - if (this.canShortCircuit == null) { - boolean canShortCircuit = true; - for (KeywordValidator validator : getEvaluationParentSchema().getValidators()) { - if ("unevaluatedProperties".equals(validator.getKeyword()) - || "unevaluatedItems".equals(validator.getKeyword())) { - canShortCircuit = false; - } - } - this.canShortCircuit = canShortCircuit; + protected boolean canShortCircuit(ExecutionContext executionContext) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext)) { + return false; } - return this.canShortCircuit; + if (hasUnevaluatedPropertiesInEvaluationPath(executionContext)) { + return false; + } + return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled(); } @Override @@ -233,8 +240,15 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root if (shouldValidateSchema && node != null) { validate(executionContext, node, rootNode, instanceLocation, true); } else { + int schemaIndex = 0; for (Schema schema : this.schemas) { - schema.walk(executionContext, node, rootNode, instanceLocation, false); + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; } } } @@ -244,6 +258,5 @@ public void preloadSchema() { for (Schema schema : this.schemas) { schema.initializeValidators(); } - canShortCircuit(); // cache the flag } } diff --git a/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java index 2af8ee6d2..6b262ed79 100644 --- a/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PatternPropertiesValidator.java @@ -34,11 +34,9 @@ public class PatternPropertiesValidator extends BaseKeywordValidator { public static final String PROPERTY = "patternProperties"; private final Map schemas = new IdentityHashMap<>(); - private Boolean hasUnevaluatedPropertiesValidator = null; - - public PatternPropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, + public PatternPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PATTERN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(KeywordType.PATTERN_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); if (!schemaNode.isObject()) { throw new SchemaException("patternProperties must be an object node"); } @@ -46,7 +44,7 @@ public PatternPropertiesValidator(SchemaLocation schemaLocation, NodePath evalua while (names.hasNext()) { String name = names.next(); RegularExpression pattern = RegularExpression.compile(name, schemaContext); - schemas.put(pattern, schemaContext.newSchema(schemaLocation.append(name), evaluationPath.append(name), + schemas.put(pattern, schemaContext.newSchema(schemaLocation.append(name), schemaNode.get(name), parentSchema)); } } @@ -59,7 +57,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } Set matchedInstancePropertyNames = null; Iterator names = node.fieldNames(); - boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext); + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) || collectAnnotations(executionContext); while (names.hasNext()) { String name = names.next(); JsonNode n = node.get(name); @@ -67,7 +65,13 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (entry.getKey().matches(name)) { NodePath path = instanceLocation.append(name); int currentErrors = executionContext.getErrors().size(); - entry.getValue().validate(executionContext, n, rootNode, path); + Schema schema = entry.getValue(); + executionContext.evaluationPathAddLast(schema.getSchemaLocation().getFragment().getElement(-1).toString()); + try { + schema.validate(executionContext, n, rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } if (currentErrors == executionContext.getErrors().size()) { // No new errors if (collectAnnotations) { if (matchedInstancePropertyNames == null) { @@ -82,7 +86,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (collectAnnotations) { executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()) .value(matchedInstancePropertyNames != null ? matchedInstancePropertyNames : Collections.emptySet()) @@ -90,20 +94,8 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } } - private boolean collectAnnotations() { - return hasUnevaluatedPropertiesValidator(); - } - - private boolean hasUnevaluatedPropertiesValidator() { - if (this.hasUnevaluatedPropertiesValidator == null) { - this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties"); - } - return hasUnevaluatedPropertiesValidator; - } - @Override public void preloadSchema() { preloadSchemas(schemas.values()); - collectAnnotations(); // cache the flag } } diff --git a/src/main/java/com/networknt/schema/keyword/PatternValidator.java b/src/main/java/com/networknt/schema/keyword/PatternValidator.java index 3af2bb054..b5fcc51b1 100644 --- a/src/main/java/com/networknt/schema/keyword/PatternValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PatternValidator.java @@ -38,8 +38,8 @@ public class PatternValidator extends BaseKeywordValidator { private final String pattern; private final RegularExpression compiledPattern; - public PatternValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PATTERN, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public PatternValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PATTERN, schemaNode, schemaLocation, parentSchema, schemaContext); this.pattern = Optional.ofNullable(schemaNode).filter(JsonNode::isTextual).map(JsonNode::textValue).orElse(null); try { @@ -67,7 +67,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode try { if (!matches(node.asText())) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(this.pattern).build()); return; } diff --git a/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java b/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java index 2db49e477..a81f6c60d 100644 --- a/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PrefixItemsValidator.java @@ -36,16 +36,14 @@ public class PrefixItemsValidator extends BaseKeywordValidator { private final List tupleSchema; - private Boolean hasUnevaluatedItemsValidator = null; - - public PrefixItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PREFIX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public PrefixItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PREFIX_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode instanceof ArrayNode && !schemaNode.isEmpty()) { int i = 0; this.tupleSchema = new ArrayList<>(schemaNode.size()); for (JsonNode s : schemaNode) { - this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), evaluationPath.append(i), s, + this.tupleSchema.add(schemaContext.newSchema(schemaLocation.append(i), s, parentSchema)); i++; } @@ -62,11 +60,16 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode int count = Math.min(node.size(), this.tupleSchema.size()); for (int i = 0; i < count; ++i) { NodePath path = instanceLocation.append(i); - this.tupleSchema.get(i).validate(executionContext, node.get(i), rootNode, path); + executionContext.evaluationPathAddLast(i); + try { + this.tupleSchema.get(i).validate(executionContext, node.get(i), rootNode, path); + } finally { + executionContext.evaluationPathRemoveLast(); + } } // Add annotation - if (collectAnnotations() || collectAnnotations(executionContext)) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { // Tuples int items = node.isArray() ? node.size() : 1; int schemas = this.tupleSchema.size(); @@ -74,13 +77,13 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // More items than schemas so the keyword only applied to the number of schemas executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(schemas).build()); } else { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -95,7 +98,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root for (int i = 0; i < count; ++i) { JsonNode n = node.get(i); if (executionContext.getWalkConfig().getApplyDefaultsStrategy().shouldApplyArrayDefaults()) { - JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i)); + JsonNode defaultNode = getDefaultNode(this.tupleSchema.get(i), executionContext); if (n != null) { // Defaults only set if array index is explicitly null if (n.isNull() && defaultNode != null) { @@ -108,7 +111,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } // Add annotation - if (collectAnnotations() || collectAnnotations(executionContext)) { + if (hasUnevaluatedItemsInEvaluationPath(executionContext) || collectAnnotations(executionContext)) { // Tuples int items = node.isArray() ? node.size() : 1; int schemas = this.tupleSchema.size(); @@ -116,13 +119,13 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // More items than schemas so the keyword only applied to the number of schemas executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(schemas).build()); } else { // Applies to all executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(true).build()); } } @@ -134,12 +137,12 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } } - private static JsonNode getDefaultNode(Schema schema) { + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { JsonNode result = schema.getSchemaNode().get("default"); if (result == null) { - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); if (schemaRef != null) { - result = getDefaultNode(schemaRef.getSchema()); + result = getDefaultNode(schemaRef.getSchema(), executionContext); } } return result; @@ -147,11 +150,11 @@ private static JsonNode getDefaultNode(Schema schema) { private void doWalk(ExecutionContext executionContext, int i, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { - walkSchema(executionContext, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i), + walkSchema(executionContext, i, this.tupleSchema.get(i), node, rootNode, instanceLocation.append(i), shouldValidateSchema); } - private void walkSchema(ExecutionContext executionContext, Schema walkSchema, JsonNode node, JsonNode rootNode, + private void walkSchema(ExecutionContext executionContext, int schemaIndex, Schema walkSchema, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean shouldValidateSchema) { //@formatter:off boolean executeWalk = executionContext.getWalkConfig().getItemWalkListenerRunner().runPreWalkListeners( @@ -164,7 +167,12 @@ private void walkSchema(ExecutionContext executionContext, Schema walkSchema, Js ); int currentErrors = executionContext.getErrors().size(); if (executeWalk) { - walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + executionContext.evaluationPathAddLast(schemaIndex); + try { + walkSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); + } finally { + executionContext.evaluationPathRemoveLast(); + } } executionContext.getWalkConfig().getItemWalkListenerRunner().runPostWalkListeners( executionContext, @@ -182,21 +190,9 @@ public List getTupleSchema() { return this.tupleSchema; } - private boolean collectAnnotations() { - return hasUnevaluatedItemsValidator(); - } - - private boolean hasUnevaluatedItemsValidator() { - if (this.hasUnevaluatedItemsValidator == null) { - this.hasUnevaluatedItemsValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedItems"); - } - return hasUnevaluatedItemsValidator; - } - @Override public void preloadSchema() { preloadSchemas(this.tupleSchema); - collectAnnotations(); // cache the flag } } diff --git a/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java index ba92c18fa..884e0e880 100644 --- a/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PropertiesValidator.java @@ -44,15 +44,13 @@ public class PropertiesValidator extends BaseKeywordValidator { public static final String PROPERTY = "properties"; private final Map schemas = new LinkedHashMap<>(); - private Boolean hasUnevaluatedPropertiesValidator; - - public PropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public PropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); for (Iterator> it = schemaNode.fields(); it.hasNext();) { Entry entry = it.next(); String pname = entry.getKey(); this.schemas.put(pname, schemaContext.newSchema(schemaLocation.append(pname), - evaluationPath.append(pname), entry.getValue(), parentSchema)); + entry.getValue(), parentSchema)); } } @@ -64,10 +62,8 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode protected void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation, boolean walk) { - - Set matchedInstancePropertyNames = null; - boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext); + boolean collectAnnotations = hasUnevaluatedPropertiesInEvaluationPath(executionContext) || collectAnnotations(executionContext); for (Entry entry : this.schemas.entrySet()) { JsonNode propertyNode = node.get(entry.getKey()); if (propertyNode != null) { @@ -78,12 +74,17 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo } matchedInstancePropertyNames.add(entry.getKey()); } - if (!walk) { - //validate the child element(s) - entry.getValue().validate(executionContext, propertyNode, rootNode, path); - } else { - // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. - walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + executionContext.evaluationPathAddLast(entry.getKey()); + try { + if (!walk) { + //validate the child element(s) + entry.getValue().validate(executionContext, propertyNode, rootNode, path); + } else { + // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. + walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + } + } finally { + executionContext.evaluationPathRemoveLast(); } } else { if (walk) { @@ -92,14 +93,20 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo // null. // The actual walk needs to be skipped as the validators assume that node is not // null. - walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + executionContext.evaluationPathAddLast(entry.getKey()); + try { + walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, + executionContext.getWalkConfig().getPropertyWalkListenerRunner()); + } finally { + executionContext.evaluationPathRemoveLast(); + } } } } if (collectAnnotations) { executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword(getKeyword()).value(matchedInstancePropertyNames == null ? Collections.emptySet() : matchedInstancePropertyNames) .build()); @@ -118,27 +125,21 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root } else { WalkListenerRunner propertyWalkListenerRunner = executionContext.getWalkConfig().getPropertyWalkListenerRunner(); for (Map.Entry entry : this.schemas.entrySet()) { - walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, propertyWalkListenerRunner); + executionContext.evaluationPathAddLast(entry.getKey()); + try { + walkSchema(executionContext, entry, node, rootNode, instanceLocation, shouldValidateSchema, propertyWalkListenerRunner); + } finally { + executionContext.evaluationPathRemoveLast(); + } } } } - private boolean collectAnnotations() { - return hasUnevaluatedPropertiesValidator(); - } - - private boolean hasUnevaluatedPropertiesValidator() { - if (this.hasUnevaluatedPropertiesValidator == null) { - this.hasUnevaluatedPropertiesValidator = hasAdjacentKeywordInEvaluationPath("unevaluatedProperties"); - } - return hasUnevaluatedPropertiesValidator; - } - private void applyPropertyDefaults(ObjectNode node, ExecutionContext executionContext) { for (Map.Entry entry : this.schemas.entrySet()) { JsonNode propertyNode = node.get(entry.getKey()); - JsonNode defaultNode = getDefaultNode(entry.getValue()); + JsonNode defaultNode = getDefaultNode(entry.getValue(), executionContext); if (defaultNode == null) { continue; } @@ -150,12 +151,12 @@ private void applyPropertyDefaults(ObjectNode node, ExecutionContext executionCo } } - private static JsonNode getDefaultNode(Schema schema) { + private static JsonNode getDefaultNode(Schema schema, ExecutionContext executionContext) { JsonNode result = schema.getSchemaNode().get("default"); if (result == null) { - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, executionContext); if (schemaRef != null) { - result = getDefaultNode(schemaRef.getSchema()); + result = getDefaultNode(schemaRef.getSchema(), executionContext); } } return result; @@ -189,6 +190,5 @@ public Map getSchemas() { @Override public void preloadSchema() { preloadSchemas(this.schemas.values()); - collectAnnotations(); // cache the flag } } diff --git a/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java index 4d13cdd42..fc257ec49 100644 --- a/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PropertyDependenciesValidator.java @@ -37,16 +37,14 @@ public class PropertyDependenciesValidator extends BaseKeywordValidator implemen */ private final Map> propertyDependencies; - public PropertyDependenciesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public PropertyDependenciesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PROPERTY_DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext, - evaluationPath); + super(KeywordType.PROPERTY_DEPENDENCIES, schemaNode, schemaLocation, parentSchema, schemaContext); Set> properties = schemaNode.properties(); this.propertyDependencies = new LinkedHashMap<>(properties.size()); for (Entry property : properties) { String propertyName = property.getKey(); SchemaLocation propertySchemaLocation = schemaLocation.append(propertyName); - NodePath propertyEvaluationPath = evaluationPath.append(propertyName); Set> propertyValues = property.getValue().properties(); for (Entry propertyValue : propertyValues) { @@ -54,7 +52,7 @@ public PropertyDependenciesValidator(SchemaLocation schemaLocation, NodePath eva key -> new LinkedHashMap<>()); valueSchemas.put(propertyValue.getKey(), schemaContext.newSchema(propertySchemaLocation.append(propertyValue.getKey()), - propertyEvaluationPath.append(propertyValue.getKey()), propertyValue.getValue(), + propertyValue.getValue(), parentSchema)); } } @@ -75,13 +73,23 @@ protected void validate(ExecutionContext executionContext, JsonNode node, JsonNo if (propertyValue != null) { Map propertySchemas = this.propertyDependencies.get(propertyName); if (propertySchemas != null) { - Schema schema = propertySchemas.get(propertyValue); - if (schema != null) { - if (!walk) { - schema.validate(executionContext, node, rootNode, instanceLocation); - } else { - schema.walk(executionContext, node, rootNode, instanceLocation, true); + executionContext.evaluationPathAddLast(propertyName); + try { + Schema schema = propertySchemas.get(propertyValue); + if (schema != null) { + executionContext.evaluationPathAddLast(propertyValue); + try { + if (!walk) { + schema.validate(executionContext, node, rootNode, instanceLocation); + } else { + schema.walk(executionContext, node, rootNode, instanceLocation, true); + } + } finally { + executionContext.evaluationPathRemoveLast(); + } } + } finally { + executionContext.evaluationPathRemoveLast(); } } } @@ -96,9 +104,20 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root return; } - for (Map properties : this.propertyDependencies.values()) { - for (Schema schema : properties.values()) { - schema.walk(executionContext, node, rootNode, instanceLocation, false); + for (Entry> property : this.propertyDependencies.entrySet()) { + String propertyName = property.getKey(); + executionContext.evaluationPathAddLast(propertyName); + try { + for (Entry propertyValue : property.getValue().entrySet()) { + executionContext.evaluationPathAddLast(propertyValue.getKey()); + try { + propertyValue.getValue().walk(executionContext, node, rootNode, instanceLocation, false); + } finally { + executionContext.evaluationPathRemoveLast(); + } + } + } finally { + executionContext.evaluationPathRemoveLast(); } } } @@ -106,6 +125,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root @Override public void preloadSchema() { for (Map properties : propertyDependencies.values()) { + for (Schema schema : properties.values()) { schema.initializeValidators(); } diff --git a/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java index a3502cee8..df64c28ca 100644 --- a/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/PropertyNamesValidator.java @@ -30,9 +30,9 @@ public class PropertyNamesValidator extends BaseKeywordValidator implements KeywordValidator { private final Schema innerSchema; - public PropertyNamesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.PROPERTY_NAMES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); - innerSchema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + public PropertyNamesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.PROPERTY_NAMES, schemaNode, schemaLocation, parentSchema, schemaContext); + innerSchema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { @@ -48,7 +48,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode for (final Error schemaError : schemaErrors) { existingErrors.add( error().property(pname).instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(pname, schemaError.getMessage()).build()); } schemaErrors.clear(); diff --git a/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java b/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java index 530711df3..cd83d4679 100644 --- a/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ReadOnlyValidator.java @@ -32,8 +32,8 @@ public class ReadOnlyValidator extends BaseKeywordValidator { private static final Logger logger = LoggerFactory.getLogger(ReadOnlyValidator.class); - public ReadOnlyValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.READ_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public ReadOnlyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.READ_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext); logger.debug("Loaded ReadOnlyValidator for property {} as {}", parentSchema, "read mode"); } @@ -42,7 +42,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (Boolean.TRUE.equals(executionContext.getExecutionConfig().getReadOnly())) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .build()); } return; diff --git a/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java b/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java index e864d3aef..158528755 100644 --- a/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RecursiveRefValidator.java @@ -24,20 +24,18 @@ import com.networknt.schema.SchemaException; import com.networknt.schema.SchemaRef; import com.networknt.schema.path.NodePath; -import com.networknt.schema.utils.ThreadSafeCachingSupplier; import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaContext; -import java.util.function.Supplier; +import java.util.Iterator; /** * {@link KeywordValidator} that resolves $recursiveRef. */ public class RecursiveRefValidator extends BaseKeywordValidator { - protected final SchemaRef schema; - public RecursiveRefValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.RECURSIVE_REF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public RecursiveRefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.RECURSIVE_REF, schemaNode, schemaLocation, parentSchema, schemaContext); String refValue = schemaNode.asText(); if (!"#".equals(refValue)) { @@ -45,34 +43,23 @@ public RecursiveRefValidator(SchemaLocation schemaLocation, NodePath evaluationP .keyword(KeywordType.RECURSIVE_REF.getValue()).messageKey("internal.invalidRecursiveRef") .message("The value of a $recursiveRef must be '#' but is '{0}'").instanceLocation(schemaLocation.getFragment()) .instanceNode(this.schemaNode) - .evaluationPath(evaluationPath).arguments(refValue).build(); + .arguments(refValue).build(); throw new SchemaException(error); } - this.schema = getRefSchema(parentSchema, schemaContext, refValue, evaluationPath); } - static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, String refValue, - NodePath evaluationPath) { - return new SchemaRef(getSupplier(() -> getSchema(parentSchema, schemaContext, refValue, evaluationPath), schemaContext.getSchemaRegistryConfig().isCacheRefs())); - } - - static Supplier getSupplier(Supplier supplier, boolean cache) { - return cache ? new ThreadSafeCachingSupplier<>(supplier) : supplier; - } - - static Schema getSchema(Schema parentSchema, SchemaContext schemaContext, String refValue, - NodePath evaluationPath) { + static Schema getSchema(Schema parentSchema, ExecutionContext executionContext) { Schema refSchema = parentSchema.findSchemaResourceRoot(); // Get the document Schema current = refSchema; Schema check = null; String base = null; String baseCheck = null; - if (refSchema != null) + if (refSchema != null) { base = current.getSchemaLocation().getAbsoluteIri() != null ? current.getSchemaLocation().getAbsoluteIri().toString() : ""; if (current.isRecursiveAnchor()) { // Check dynamic scope - while (current.getEvaluationParentSchema() != null) { - current = current.getEvaluationParentSchema(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + current = iter.next(); baseCheck = current.getSchemaLocation().getAbsoluteIri() != null ? current.getSchemaLocation().getAbsoluteIri().toString() : ""; if (!base.equals(baseCheck)) { base = baseCheck; @@ -84,20 +71,17 @@ static Schema getSchema(Schema parentSchema, SchemaContext schemaContext, String } } } - if (refSchema != null) { - refSchema = refSchema.fromRef(parentSchema, evaluationPath); } return refSchema; } @Override public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { - - Schema refSchema = this.schema.getSchema(); + Schema refSchema = getSchemaRef(executionContext).getSchema(); if (refSchema == null) { Error error = error().keyword(KeywordType.RECURSIVE_REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } @@ -110,21 +94,20 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root // This is important because if we use same JsonSchemaFactory for creating multiple JSONSchema instances, // these schemas will be cached along with config. We have to replace the config for cached $ref references // with the latest config. Reset the config. - Schema refSchema = this.schema.getSchema(); + Schema refSchema = getSchemaRef(executionContext).getSchema(); if (refSchema == null) { Error error = error().keyword(KeywordType.RECURSIVE_REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } if (node == null) { // Check for circular dependency - SchemaLocation schemaLocation = refSchema.getSchemaLocation(); - Schema check = refSchema; boolean circularDependency = false; - while (check.getEvaluationParentSchema() != null) { - check = check.getEvaluationParentSchema(); + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); if (check.getSchemaLocation().equals(schemaLocation)) { circularDependency = true; break; @@ -137,39 +120,7 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root refSchema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema); } - public SchemaRef getSchemaRef() { - return this.schema; - } - - @Override - public void preloadSchema() { - Schema jsonSchema = null; - try { - jsonSchema = this.schema.getSchema(); - } catch (SchemaException e) { - throw e; - } catch (RuntimeException e) { - throw new SchemaException(e); - } - // Check for circular dependency - // Only one cycle is pre-loaded - // The rest of the cycles will load at execution time depending on the input - // data - SchemaLocation schemaLocation = jsonSchema.getSchemaLocation(); - Schema check = jsonSchema; - boolean circularDependency = false; - int depth = 0; - while (check.getEvaluationParentSchema() != null) { - depth++; - check = check.getEvaluationParentSchema(); - if (check.getSchemaLocation().equals(schemaLocation)) { - circularDependency = true; - break; - } - } - if (this.schemaContext.getSchemaRegistryConfig().isCacheRefs() && !circularDependency - && depth < this.schemaContext.getSchemaRegistryConfig().getPreloadSchemaRefMaxNestingDepth()) { - jsonSchema.initializeValidators(); - } + public SchemaRef getSchemaRef(ExecutionContext executionContext) { + return new SchemaRef(() -> getSchema(this.parentSchema, executionContext)); } } diff --git a/src/main/java/com/networknt/schema/keyword/RefValidator.java b/src/main/java/com/networknt/schema/keyword/RefValidator.java index 105744cb8..4ea868615 100644 --- a/src/main/java/com/networknt/schema/keyword/RefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RefValidator.java @@ -28,6 +28,7 @@ import com.networknt.schema.SchemaLocation; import com.networknt.schema.SchemaContext; +import java.util.Iterator; import java.util.function.Supplier; /** @@ -38,14 +39,13 @@ public class RefValidator extends BaseKeywordValidator { private static final String REF_CURRENT = "#"; - public RefValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.REF, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public RefValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.REF, schemaNode, schemaLocation, parentSchema, schemaContext); String refValue = schemaNode.asText(); - this.schema = getRefSchema(parentSchema, schemaContext, refValue, evaluationPath); + this.schema = getRefSchema(parentSchema, schemaContext, refValue); } - static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, String refValue, - NodePath evaluationPath) { + static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, String refValue) { // The evaluationPath is used to derive the keywordLocation final String refValueOriginal = refValue; @@ -76,7 +76,7 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, if (schemaResource == null) { return null; } - return schemaResource.fromRef(parentSchema, evaluationPath); + return schemaResource; } else { String newRefValue = refValue.substring(index); String find = schemaLocation.getAbsoluteIri() + newRefValue; @@ -87,13 +87,13 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, if (findSchemaResource != null) { schemaResource = findSchemaResource; } else { - schemaResource = getSchema(schemaResource, schemaContext, newRefValue, refValueOriginal, - evaluationPath); + schemaResource = getSchema(schemaResource, schemaContext, newRefValue, refValueOriginal + ); } if (schemaResource == null) { return null; } - return schemaResource.fromRef(parentSchema, evaluationPath); + return schemaResource; } }, schemaContext.getSchemaRegistryConfig().isCacheRefs())); @@ -106,22 +106,21 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, schemaResource = schemaContext.getDynamicAnchors().get(absoluteIri); } if (schemaResource == null) { - schemaResource = getSchema(parentSchema, schemaContext, refValue, refValueOriginal, evaluationPath); + schemaResource = getSchema(parentSchema, schemaContext, refValue, refValueOriginal); } if (schemaResource == null) { return null; } - return schemaResource.fromRef(parentSchema, evaluationPath); + return schemaResource; }, schemaContext.getSchemaRegistryConfig().isCacheRefs())); } if (refValue.equals(REF_CURRENT)) { return new SchemaRef( - getSupplier(() -> parentSchema.findSchemaResourceRoot().fromRef(parentSchema, evaluationPath), + getSupplier(() -> parentSchema.findSchemaResourceRoot(), schemaContext.getSchemaRegistryConfig().isCacheRefs())); } return new SchemaRef(getSupplier( - () -> getSchema(parentSchema, schemaContext, refValue, refValueOriginal, evaluationPath) - .fromRef(parentSchema, evaluationPath), + () -> getSchema(parentSchema, schemaContext, refValue, refValueOriginal), schemaContext.getSchemaRegistryConfig().isCacheRefs())); } @@ -156,16 +155,16 @@ private static String resolve(Schema parentSchema, String refValue) { private static Schema getSchema(Schema parent, SchemaContext schemaContext, String refValue, - String refValueOriginal, - NodePath evaluationPath) { - // This should be processing json pointer fragments only - NodePath fragment = SchemaLocation.Fragment.of(refValue); + String refValueOriginal + ) { String schemaReference = resolve(parent, refValueOriginal); // ConcurrentHashMap computeIfAbsent does not allow calls that result in a // recursive update to the map. // The getSubSchema potentially recurses to call back to getJsonSchema again Schema result = schemaContext.getSchemaReferences().get(schemaReference); if (result == null) { + // This should be processing json pointer fragments only + NodePath fragment = SchemaLocation.Fragment.of(refValue); synchronized (schemaContext.getSchemaRegistry()) { // acquire lock on shared factory object to prevent deadlock result = schemaContext.getSchemaReferences().get(schemaReference); if (result == null) { @@ -186,7 +185,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (refSchema == null) { Error error = error().keyword(KeywordType.REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } @@ -203,17 +202,16 @@ public void walk(ExecutionContext executionContext, JsonNode node, JsonNode root if (refSchema == null) { Error error = error().keyword(KeywordType.REF.getValue()) .messageKey("internal.unresolvedRef").message("Reference {0} cannot be resolved") - .instanceLocation(instanceLocation).evaluationPath(getEvaluationPath()) + .instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .arguments(schemaNode.asText()).build(); throw new InvalidSchemaRefException(error); } if (node == null) { // Check for circular dependency - SchemaLocation schemaLocation = refSchema.getSchemaLocation(); - Schema check = refSchema; boolean circularDependency = false; - while (check.getEvaluationParentSchema() != null) { - check = check.getEvaluationParentSchema(); + SchemaLocation schemaLocation = refSchema.getSchemaLocation(); + for (Iterator iter = executionContext.getEvaluationSchema().descendingIterator(); iter.hasNext();) { + Schema check = iter.next(); if (check.getSchemaLocation().equals(schemaLocation)) { circularDependency = true; break; @@ -240,10 +238,12 @@ public void preloadSchema() { } catch (RuntimeException e) { throw new SchemaException(e); } + jsonSchema.initializeValidators(); // Check for circular dependency // Only one cycle is pre-loaded // The rest of the cycles will load at execution time depending on the input // data + /* SchemaLocation schemaLocation = jsonSchema.getSchemaLocation(); Schema check = jsonSchema; boolean circularDependency = false; @@ -260,5 +260,6 @@ public void preloadSchema() { && depth < this.schemaContext.getSchemaRegistryConfig().getPreloadSchemaRefMaxNestingDepth()) { jsonSchema.initializeValidators(); } + */ } } diff --git a/src/main/java/com/networknt/schema/keyword/RequiredValidator.java b/src/main/java/com/networknt/schema/keyword/RequiredValidator.java index ca5c337b8..7f4dde24e 100644 --- a/src/main/java/com/networknt/schema/keyword/RequiredValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RequiredValidator.java @@ -31,8 +31,8 @@ public class RequiredValidator extends BaseKeywordValidator implements KeywordValidator { private final List fieldNames; - public RequiredValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public RequiredValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.REQUIRED, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.isArray()) { this.fieldNames = new ArrayList<>(schemaNode.size()); for (JsonNode fieldNme : schemaNode) { @@ -73,7 +73,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode * @see Basic */ executionContext.addError(error().instanceNode(node).property(fieldName).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(fieldName).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/TrueValidator.java b/src/main/java/com/networknt/schema/keyword/TrueValidator.java index 71347de9d..fad0d0573 100644 --- a/src/main/java/com/networknt/schema/keyword/TrueValidator.java +++ b/src/main/java/com/networknt/schema/keyword/TrueValidator.java @@ -26,8 +26,8 @@ * {@link KeywordValidator} for true. */ public class TrueValidator extends BaseKeywordValidator implements KeywordValidator { - public TrueValidator(SchemaLocation schemaLocation, NodePath evaluationPath, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.TRUE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public TrueValidator(SchemaLocation schemaLocation, final JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TRUE, schemaNode, schemaLocation, parentSchema, schemaContext); } public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { diff --git a/src/main/java/com/networknt/schema/keyword/TypeValidator.java b/src/main/java/com/networknt/schema/keyword/TypeValidator.java index 0c67413c6..70abe92e6 100644 --- a/src/main/java/com/networknt/schema/keyword/TypeValidator.java +++ b/src/main/java/com/networknt/schema/keyword/TypeValidator.java @@ -33,11 +33,11 @@ public class TypeValidator extends BaseKeywordValidator { private final JsonType schemaType; private final UnionTypeValidator unionTypeValidator; - public TypeValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public TypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); this.schemaType = TypeFactory.getSchemaNodeType(schemaNode); if (this.schemaType == JsonType.UNION) { - this.unionTypeValidator = new UnionTypeValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, schemaContext); + this.unionTypeValidator = new UnionTypeValidator(schemaLocation, schemaNode, parentSchema, schemaContext); } else { this.unionTypeValidator = null; } @@ -47,8 +47,8 @@ public JsonType getSchemaType() { return this.schemaType; } - public boolean equalsToSchemaType(JsonNode node) { - return JsonNodeTypes.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.schemaContext); + public boolean equalsToSchemaType(JsonNode node, ExecutionContext executionContext) { + return JsonNodeTypes.equalsToSchemaType(node, this.schemaType, this.parentSchema, this.schemaContext, executionContext); } @Override @@ -60,10 +60,10 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode return; } - if (!equalsToSchemaType(node)) { + if (!equalsToSchemaType(node, executionContext)) { JsonType nodeType = TypeFactory.getValueNodeType(node, this.schemaContext.getSchemaRegistryConfig()); executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(nodeType.toString(), this.schemaType.toString()).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java b/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java index f596ad7f9..ddeedd319 100644 --- a/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/UnevaluatedItemsValidator.java @@ -38,13 +38,12 @@ public class UnevaluatedItemsValidator extends BaseKeywordValidator { private final boolean isMinV202012; - public UnevaluatedItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public UnevaluatedItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.UNEVALUATED_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, - evaluationPath); + super(KeywordType.UNEVALUATED_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); isMinV202012 = MIN_DRAFT_2020_12.getVersions().contains(schemaContext.getDialect().getSpecificationVersion()); if (schemaNode.isObject() || schemaNode.isBoolean()) { - this.schema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } else { throw new IllegalArgumentException("The value of 'unevaluatedItems' MUST be a valid JSON Schema."); } @@ -73,10 +72,11 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode boolean evaluated = false; // Get all the valid adjacent annotations - Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); + Predicate validEvaluationPathFilter = a -> a.isValid(); + //Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); Predicate adjacentEvaluationPathFilter = a -> a.getEvaluationPath() - .startsWith(this.evaluationPath.getParent()); + .startsWith(executionContext.getEvaluationPath().getParent()); List instanceLocationAnnotations = executionContext.getAnnotations().asMap() .getOrDefault(instanceLocation, Collections.emptyList()); @@ -173,7 +173,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) { // All fails as "unevaluatedItems: false" executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation).arguments(x) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .build()); } else { // Schema errors will be reported as is @@ -196,7 +196,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (evaluated) { executionContext.getAnnotations() .put(Annotation.builder().instanceLocation(instanceLocation) - .evaluationPath(this.evaluationPath).schemaLocation(this.schemaLocation) + .evaluationPath(executionContext.getEvaluationPath()).schemaLocation(this.schemaLocation) .keyword("unevaluatedItems").value(true).build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java b/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java index fbdfa4dee..f92f15dd5 100644 --- a/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/keyword/UnevaluatedPropertiesValidator.java @@ -38,11 +38,11 @@ public class UnevaluatedPropertiesValidator extends BaseKeywordValidator { private final Schema schema; - public UnevaluatedPropertiesValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.UNEVALUATED_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public UnevaluatedPropertiesValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.UNEVALUATED_PROPERTIES, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.isObject() || schemaNode.isBoolean()) { - this.schema = schemaContext.newSchema(schemaLocation, evaluationPath, schemaNode, parentSchema); + this.schema = schemaContext.newSchema(schemaLocation, schemaNode, parentSchema); } else { throw new IllegalArgumentException("The value of 'unevaluatedProperties' MUST be a valid JSON Schema."); } @@ -56,10 +56,11 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode // Get all the valid adjacent annotations - Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); + Predicate validEvaluationPathFilter = a -> a.isValid(); + //Predicate validEvaluationPathFilter = a -> executionContext.getInstanceResults().isValid(instanceLocation, a.getEvaluationPath()); Predicate adjacentEvaluationPathFilter = a -> a.getEvaluationPath() - .startsWith(this.evaluationPath.getParent()); + .startsWith(executionContext.getEvaluationPath().getParent()); List instanceLocationAnnotations = executionContext.getAnnotations().asMap() .getOrDefault(instanceLocation, Collections.emptyList()); @@ -121,7 +122,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) { // All fails as "unevaluatedProperties: false" executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation).property(fieldName) - .arguments(fieldName).locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).arguments(fieldName).locale(executionContext.getExecutionConfig().getLocale()) .build()); } else { // Schema errors will be reported as is @@ -134,7 +135,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode executionContext.setFailFast(failFast); // restore flag } executionContext.getAnnotations() - .put(Annotation.builder().instanceLocation(instanceLocation).evaluationPath(this.evaluationPath) + .put(Annotation.builder().instanceLocation(instanceLocation).evaluationPath(executionContext.getEvaluationPath()) .schemaLocation(this.schemaLocation).keyword(getKeyword()).value(evaluatedProperties).build()); return; diff --git a/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java b/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java index c42b3018b..be4f29429 100644 --- a/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java +++ b/src/main/java/com/networknt/schema/keyword/UnionTypeValidator.java @@ -38,8 +38,8 @@ public class UnionTypeValidator extends BaseKeywordValidator implements KeywordV private final List schemas; private final String error; - public UnionTypeValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public UnionTypeValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.TYPE, schemaNode, schemaLocation, parentSchema, schemaContext); StringBuilder errorBuilder = new StringBuilder(); String sep = ""; @@ -57,11 +57,9 @@ public UnionTypeValidator(SchemaLocation schemaLocation, NodePath evaluationPath sep = ", "; if (n.isObject()) { - schemas.add(schemaContext.newSchema(schemaLocation.append(KeywordType.TYPE.getValue()), - evaluationPath.append(KeywordType.TRUE.getValue()), n, parentSchema)); + schemas.add(schemaContext.newSchema(schemaLocation.append(i), n, parentSchema)); } else { - schemas.add(new TypeValidator(schemaLocation.append(i), evaluationPath.append(i), n, parentSchema, - schemaContext)); + schemas.add(new TypeValidator(schemaLocation.append(i), n, parentSchema, schemaContext)); } i++; } @@ -85,8 +83,15 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode List test = new ArrayList<>(); executionContext.setFailFast(false); executionContext.setErrors(test); + int schemaIndex = 0; for (Validator schema : schemas) { - schema.validate(executionContext, node, rootNode, instanceLocation); + executionContext.evaluationPathAddLast(schemaIndex); + try { + schema.validate(executionContext, node, rootNode, instanceLocation); + } finally { + executionContext.evaluationPathRemoveLast(); + } + schemaIndex++; if (test.isEmpty()) { valid = true; break; @@ -103,7 +108,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (!valid) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) .keyword("type") - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .arguments(nodeType.toString(), error) .build()); } diff --git a/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java index 5ee628164..05f5bc080 100644 --- a/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java +++ b/src/main/java/com/networknt/schema/keyword/UniqueItemsValidator.java @@ -32,8 +32,8 @@ public class UniqueItemsValidator extends BaseKeywordValidator implements KeywordValidator { private final boolean unique; - public UniqueItemsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.UNIQUE_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public UniqueItemsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.UNIQUE_ITEMS, schemaNode, schemaLocation, parentSchema, schemaContext); if (schemaNode.isBoolean()) { unique = schemaNode.booleanValue(); } else { @@ -49,7 +49,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode for (JsonNode n : node) { if (!set.add(n)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .build()); } } diff --git a/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java b/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java index e7bc5db9c..3537395d6 100644 --- a/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java +++ b/src/main/java/com/networknt/schema/keyword/WriteOnlyValidator.java @@ -16,8 +16,8 @@ public class WriteOnlyValidator extends BaseKeywordValidator { private static final Logger logger = LoggerFactory.getLogger(WriteOnlyValidator.class); - public WriteOnlyValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { - super(KeywordType.WRITE_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + public WriteOnlyValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + super(KeywordType.WRITE_ONLY, schemaNode, schemaLocation, parentSchema, schemaContext); logger.debug("Loaded WriteOnlyValidator for property {} as {}", parentSchema, "write mode"); } @@ -26,7 +26,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (Boolean.TRUE.equals(executionContext.getExecutionConfig().getWriteOnly())) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .locale(executionContext.getExecutionConfig().getLocale()) + .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) .build()); } } diff --git a/src/main/java/com/networknt/schema/output/OutputUnitData.java b/src/main/java/com/networknt/schema/output/OutputUnitData.java index a2464e9cb..b76e8ded4 100644 --- a/src/main/java/com/networknt/schema/output/OutputUnitData.java +++ b/src/main/java/com/networknt/schema/output/OutputUnitData.java @@ -100,8 +100,9 @@ public static OutputUnitData from(List validationErrors, ExecutionContext OutputUnitKey key = new OutputUnitKey(annotation.getEvaluationPath().getParent(), annotationSchemaLocation, annotation.getInstanceLocation()); - boolean validResult = executionContext.getInstanceResults().isValid(annotation.getInstanceLocation(), - annotation.getEvaluationPath()); +// boolean validResult = executionContext.getInstanceResults().isValid(annotation.getInstanceLocation(), +// annotation.getEvaluationPath()); + boolean validResult = annotation.isValid(); valid.put(key, validResult); if (validResult) { // annotations diff --git a/src/main/java/com/networknt/schema/path/NodePath.java b/src/main/java/com/networknt/schema/path/NodePath.java index 8e4cd118d..bb0d953d2 100644 --- a/src/main/java/com/networknt/schema/path/NodePath.java +++ b/src/main/java/com/networknt/schema/path/NodePath.java @@ -21,7 +21,7 @@ /** * Represents a path to a JSON node. */ -public class NodePath implements Comparable { +public class NodePath implements Comparable, Path { private final PathType type; private final NodePath parent; diff --git a/src/main/java/com/networknt/schema/path/Path.java b/src/main/java/com/networknt/schema/path/Path.java new file mode 100644 index 000000000..c72cc8393 --- /dev/null +++ b/src/main/java/com/networknt/schema/path/Path.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 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.path; + +/** + * Represents a path. + */ +public interface Path { + String toString(); +} diff --git a/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java index cdd9bd818..9062aa8e1 100644 --- a/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java +++ b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java @@ -1,6 +1,7 @@ package com.networknt.schema.utils; import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.ExecutionContext; import com.networknt.schema.Schema; import com.networknt.schema.SchemaContext; import com.networknt.schema.SchemaRegistryConfig; @@ -16,10 +17,10 @@ public class JsonNodeTypes { public static boolean isNodeNullable(JsonNode schema){ JsonNode nullable = schema.get(NULLABLE); - return nullable != null && nullable.asBoolean(); + return nullable != null && nullable.asBoolean(); } - public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, Schema parentSchema, SchemaContext schemaContext) { + public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, Schema parentSchema, SchemaContext schemaContext, ExecutionContext executionContext) { SchemaRegistryConfig config = schemaContext.getSchemaRegistryConfig(); JsonType nodeType = TypeFactory.getValueNodeType(node, config); // in the case that node type is not the same as schema type, try to convert node to the @@ -49,21 +50,21 @@ public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, Sch // Skip the type validation when the schema is an enum object schema. Since the current type // of node itself can be used for type validation. - if (isEnumObjectSchema(parentSchema) && !config.isStrict("type", Boolean.TRUE)) { + if (!config.isStrict("type", Boolean.TRUE) && isEnumObjectSchema(parentSchema, executionContext)) { return true; } - if (config != null && config.isTypeLoose()) { + if (config.isTypeLoose()) { // if typeLoose is true, everything can be a size 1 array if (schemaType == JsonType.ARRAY) { return true; } if (nodeType == JsonType.STRING) { if (schemaType == JsonType.INTEGER) { - return Strings.isInteger(node.textValue()); + return Strings.isInteger(node.textValue()); } else if (schemaType == JsonType.BOOLEAN) { - return Strings.isBoolean(node.textValue()); + return Strings.isBoolean(node.textValue()); } else if (schemaType == JsonType.NUMBER) { - return Strings.isNumeric(node.textValue()); + return Strings.isNumeric(node.textValue()); } } } @@ -96,7 +97,8 @@ public static boolean isNumber(JsonNode node, SchemaRegistryConfig config) { return false; } - private static boolean isEnumObjectSchema(Schema jsonSchema) { + private static boolean isEnumObjectSchema(Schema jsonSchema, ExecutionContext executionContext) { + // There are three conditions for enum object schema // 1. The current schema contains key "type", and the value is object // 2. The current schema contains key "enum", and the value is an array @@ -110,7 +112,7 @@ private static boolean isEnumObjectSchema(Schema jsonSchema) { typeNode = jsonSchema.getSchemaNode().get(TYPE); enumNode = jsonSchema.getSchemaNode().get(ENUM); } - refNode = REF.equals(jsonSchema.getEvaluationPath().getElement(-1)); + refNode = REF.equals(executionContext.getEvaluationPath().getParent().getElement(-1)); } if (typeNode != null && enumNode != null && refNode) { return TypeFactory.getSchemaNodeType(typeNode) == JsonType.OBJECT && enumNode.isArray(); diff --git a/src/main/java/com/networknt/schema/utils/SchemaRefs.java b/src/main/java/com/networknt/schema/utils/SchemaRefs.java index fa8d8ff14..e6782f55e 100644 --- a/src/main/java/com/networknt/schema/utils/SchemaRefs.java +++ b/src/main/java/com/networknt/schema/utils/SchemaRefs.java @@ -15,6 +15,7 @@ */ package com.networknt.schema.utils; +import com.networknt.schema.ExecutionContext; import com.networknt.schema.Schema; import com.networknt.schema.SchemaRef; import com.networknt.schema.keyword.DynamicRefValidator; @@ -33,14 +34,14 @@ public class SchemaRefs { * @param schema the schema * @return the ref */ - public static SchemaRef from(Schema schema) { + public static SchemaRef from(Schema schema, ExecutionContext executionContext) { for (KeywordValidator validator : schema.getValidators()) { if (validator instanceof RefValidator) { return ((RefValidator) validator).getSchemaRef(); } else if (validator instanceof DynamicRefValidator) { - return ((DynamicRefValidator) validator).getSchemaRef(); + return ((DynamicRefValidator) validator).getSchemaRef(executionContext); } else if (validator instanceof RecursiveRefValidator) { - return ((RecursiveRefValidator) validator).getSchemaRef(); + return ((RecursiveRefValidator) validator).getSchemaRef(executionContext); } } return null; diff --git a/src/main/java/com/networknt/schema/utils/Strings.java b/src/main/java/com/networknt/schema/utils/Strings.java index 0858655a3..d16f7202e 100644 --- a/src/main/java/com/networknt/schema/utils/Strings.java +++ b/src/main/java/com/networknt/schema/utils/Strings.java @@ -16,6 +16,9 @@ package com.networknt.schema.utils; +import java.util.ArrayList; +import java.util.List; + /** * Utility methods for working with Strings. */ @@ -126,4 +129,44 @@ public static boolean isNumeric(String string) { public static boolean isBlank(String string) { return null == string || string.trim().isEmpty(); } + + /** + * Split text. Unlike the JDK String split using regex trailing delimiters are + * preserved. + * + * @param text the text to split + * @param delimiter the delimiter + * @return the fragments + */ + public static String[] split(String text, char delimiter) { + if (text == null) { + return new String[0]; + } + if (text.isEmpty()) { + return new String[]{""}; + } + + List segments = new ArrayList<>(); + int start = 0; + int end; + + while (start <= text.length()) { + end = text.indexOf(delimiter, start); + + if (end == -1) { + end = text.length(); + } + + String segment = text.substring(start, end); + segments.add(segment); + + if (end == text.length()) { + break; + } + + start = end + 1; + } + + return segments.toArray(new String[segments.size()]); + } } diff --git a/src/main/java/com/networknt/schema/utils/TypeFactory.java b/src/main/java/com/networknt/schema/utils/TypeFactory.java index ec2808c0c..ffb1c4f35 100644 --- a/src/main/java/com/networknt/schema/utils/TypeFactory.java +++ b/src/main/java/com/networknt/schema/utils/TypeFactory.java @@ -93,8 +93,7 @@ public static JsonType getValueNodeType(JsonNode node, SchemaRegistryConfig conf case NUMBER: if (node.isIntegralNumber()) { return JsonType.INTEGER; - } else if (config != null && (config.isJavaSemantics() || config.isLosslessNarrowing()) - && node.canConvertToExactIntegral()) { + } else if (config != null && config.isLosslessNarrowing() && node.canConvertToExactIntegral()) { return JsonType.INTEGER; } else { return JsonType.NUMBER; diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index 93549d4fe..cdce63b28 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -18,6 +18,11 @@ public class WalkEvent { private JsonNode instanceNode; private NodePath instanceLocation; private KeywordValidator validator; + private NodePath evaluationPath; + + public NodePath getEvaluationPath() { + return this.evaluationPath; + } /** * Gets the execution context. @@ -93,7 +98,7 @@ public T getValidator() { @Override public String toString() { - return "WalkEvent [evaluationPath=" + getSchema().getEvaluationPath() + ", schemaLocation=" + return "WalkEvent [schemaLocation=" + getSchema().getSchemaLocation() + ", instanceLocation=" + instanceLocation + "]"; } @@ -141,6 +146,9 @@ public WalkEventBuilder validator(KeywordValidator validator) { } public WalkEvent build() { + if (walkEvent.executionContext != null) { + walkEvent.evaluationPath = walkEvent.executionContext.getEvaluationPath(); + } return walkEvent; } diff --git a/src/test/java/com/networknt/schema/CollectorContextTest.java b/src/test/java/com/networknt/schema/CollectorContextTest.java index f2ecfc7e7..0d5ada6e2 100644 --- a/src/test/java/com/networknt/schema/CollectorContextTest.java +++ b/src/test/java/com/networknt/schema/CollectorContextTest.java @@ -260,10 +260,10 @@ public String getValue() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator(schemaLocation, evaluationPath, schemaNode); + return new CustomValidator(schemaLocation, schemaNode); } return null; } @@ -277,8 +277,8 @@ public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath eva */ private class CustomValidator extends AbstractKeywordValidator { private final CustomCollector customCollector = new CustomCollector(); - public CustomValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode) { - super(new CustomKeyword(), schemaNode, schemaLocation, evaluationPath); + public CustomValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode, schemaLocation); } @Override @@ -350,10 +350,10 @@ public String getValue() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator1(schemaLocation, evaluationPath, schemaNode); + return new CustomValidator1(schemaLocation, schemaNode); } return null; } @@ -368,8 +368,8 @@ public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath eva * keyword has been used multiple times in JSON Schema. */ private class CustomValidator1 extends AbstractKeywordValidator { - public CustomValidator1(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode) { - super(new CustomKeyword(), schemaNode,schemaLocation, evaluationPath); + public CustomValidator1(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode,schemaLocation); } @Override @@ -426,18 +426,18 @@ public String getValue() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { if (schemaNode != null && schemaNode.isBoolean()) { - return new CollectValidator(schemaLocation, evaluationPath, schemaNode); + return new CollectValidator(schemaLocation, schemaNode); } return null; } } private class CollectValidator extends AbstractKeywordValidator { - CollectValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode) { - super(new CollectKeyword(), schemaNode, schemaLocation, evaluationPath); + CollectValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CollectKeyword(), schemaNode, schemaLocation); } @Override diff --git a/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java b/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java index 7f7c06c00..50fde8bac 100644 --- a/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java +++ b/src/test/java/com/networknt/schema/CustomMetaSchemaTest.java @@ -52,9 +52,9 @@ private static final class Validator extends AbstractKeywordValidator { private final List enumNames; private final String keyword; - private Validator(SchemaLocation schemaLocation, NodePath evaluationPath, String keyword, + private Validator(SchemaLocation schemaLocation, String keyword, List enumValues, List enumNames, JsonNode schemaNode) { - super(new EnumNamesKeyword(), schemaNode, schemaLocation, evaluationPath); + super(new EnumNamesKeyword(), schemaNode, schemaLocation); if (enumNames.size() != enumValues.size()) { throw new IllegalArgumentException("enum and enumNames need to be of same length"); } @@ -86,7 +86,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { /* * You can access the schema node here to read data from your keyword @@ -100,7 +100,7 @@ public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath eva } JsonNode enumSchemaNode = parentSchemaNode.get("enum"); - return new Validator(schemaLocation, evaluationPath, getValue(), readStringList(enumSchemaNode), + return new Validator(schemaLocation, getValue(), readStringList(enumSchemaNode), readStringList(schemaNode), schemaNode); } diff --git a/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java b/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java index e780962d2..cbdc6e260 100644 --- a/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java +++ b/src/test/java/com/networknt/schema/FormatKeywordFactoryTest.java @@ -27,7 +27,6 @@ import com.networknt.schema.format.Format; import com.networknt.schema.keyword.FormatKeyword; import com.networknt.schema.keyword.KeywordValidator; -import com.networknt.schema.path.NodePath; class FormatKeywordFactoryTest { @@ -37,7 +36,7 @@ public CustomFormatKeyword(Map formats) { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) { throw new IllegalArgumentException(); } } diff --git a/src/test/java/com/networknt/schema/IfValidatorTest.java b/src/test/java/com/networknt/schema/IfValidatorTest.java index 68b3a593c..c36b231d5 100644 --- a/src/test/java/com/networknt/schema/IfValidatorTest.java +++ b/src/test/java/com/networknt/schema/IfValidatorTest.java @@ -79,7 +79,7 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { List types = result.getExecutionContext().getCollectorContext().get("types"); assertEquals(1, types.size()); assertEquals("", types.get(0).getInstanceLocation().toString()); - assertEquals("/then", types.get(0).getSchema().getEvaluationPath().toString()); + assertEquals("/then", types.get(0).getEvaluationPath().toString()); } @Test @@ -125,7 +125,7 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { List types = (List) result.getExecutionContext().getCollectorContext().get("types"); assertEquals(1, types.size()); assertEquals("", types.get(0).getInstanceLocation().toString()); - assertEquals("/else", types.get(0).getSchema().getEvaluationPath().toString()); + assertEquals("/else", types.get(0).getEvaluationPath().toString()); } @Test diff --git a/src/test/java/com/networknt/schema/Issue1091Test.java b/src/test/java/com/networknt/schema/Issue1091Test.java index 854e0e538..450d6e164 100644 --- a/src/test/java/com/networknt/schema/Issue1091Test.java +++ b/src/test/java/com/networknt/schema/Issue1091Test.java @@ -18,30 +18,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; -import java.util.stream.Collectors; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.serialization.JsonMapperFactory; - class Issue1091Test { @Test @Disabled // Disabled as this test takes quite long to run for ci void testHasAdjacentKeywordInEvaluationPath() throws Exception { - SchemaRegistryConfig config = SchemaRegistryConfig.builder().cacheRefs(false).build(); - - Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)) + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4) .getSchema(SchemaLocation.of("classpath:schema/issue1091.json")); - JsonNode node = JsonMapperFactory.getInstance() - .readTree(Issue1091Test.class.getClassLoader().getResource("data/issue1091.json")); - - List messages = schema.validate(node) - .stream() - .map(Error::getMessage) - .collect(Collectors.toList()); - - assertEquals(0, messages.size()); + List errors = schema.validate(AbsoluteIri.of("classpath:data/issue1091.json"), InputFormat.JSON); + assertEquals(0, errors.size()); } } diff --git a/src/test/java/com/networknt/schema/Issue467Test.java b/src/test/java/com/networknt/schema/Issue467Test.java index f32c0bb32..59259cda0 100644 --- a/src/test/java/com/networknt/schema/Issue467Test.java +++ b/src/test/java/com/networknt/schema/Issue467Test.java @@ -53,7 +53,7 @@ void shouldWalkKeywordWithValidation() throws URISyntaxException, IOException { .keywordWalkListener(KeywordType.PROPERTIES.getValue(), new WalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - properties.add(walkEvent.getSchema().getEvaluationPath().append(walkEvent.getKeyword())); + properties.add(walkEvent.getExecutionContext().getEvaluationPath().append(walkEvent.getKeyword())); return WalkFlow.CONTINUE; } @@ -82,7 +82,7 @@ void shouldWalkPropertiesWithValidation() throws URISyntaxException, IOException .propertyWalkListener(new WalkListener() { @Override public WalkFlow onWalkStart(WalkEvent walkEvent) { - properties.add(walkEvent.getSchema().getEvaluationPath()); + properties.add(walkEvent.getExecutionContext().getEvaluationPath()); return WalkFlow.CONTINUE; } diff --git a/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java b/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java index 5bb119753..bec207c8e 100644 --- a/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java +++ b/src/test/java/com/networknt/schema/JsonSchemaPreloadTest.java @@ -32,7 +32,6 @@ void cacheRefsFalse() { @Test void preloadSchemaRefMaxNestingDepth() { SchemaRegistryConfig config = SchemaRegistryConfig.builder() - .preloadSchemaRefMaxNestingDepth(20) .build(); SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, builder -> builder.schemaRegistryConfig(config)); factory.getSchema(SchemaLocation.of("classpath:/issues/1016/schema.json")); diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 87329f64e..27ff74ca8 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -165,10 +165,10 @@ public String getValue() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException { if (schemaNode != null && schemaNode.isArray()) { - return new CustomValidator(schemaLocation, evaluationPath, schemaNode); + return new CustomValidator(schemaLocation, schemaNode); } return null; } @@ -181,8 +181,8 @@ public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath eva */ private static class CustomValidator extends AbstractKeywordValidator { - CustomValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode) { - super(new CustomKeyword(), schemaNode, schemaLocation, evaluationPath); + CustomValidator(SchemaLocation schemaLocation, JsonNode schemaNode) { + super(new CustomKeyword(), schemaNode, schemaLocation); } @Override diff --git a/src/test/java/com/networknt/schema/MessageTest.java b/src/test/java/com/networknt/schema/MessageTest.java index 3e2a08f70..7a9f42629 100644 --- a/src/test/java/com/networknt/schema/MessageTest.java +++ b/src/test/java/com/networknt/schema/MessageTest.java @@ -36,10 +36,10 @@ class MessageTest { static class EqualsValidator extends BaseKeywordValidator { private final String value; - EqualsValidator(SchemaLocation schemaLocation, NodePath evaluationPath, JsonNode schemaNode, + EqualsValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, Keyword keyword, SchemaContext schemaContext) { - super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext, evaluationPath); + super(keyword, schemaNode, schemaLocation, parentSchema, schemaContext); this.value = schemaNode.textValue(); } @@ -49,7 +49,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode if (!node.asText().equals(value)) { executionContext.addError(error().message("must be equal to ''{0}''") .arguments(value) - .instanceLocation(instanceLocation).instanceNode(node).build()); + .instanceLocation(instanceLocation).instanceNode(node).evaluationPath(executionContext.getEvaluationPath()).build()); } } } @@ -62,10 +62,10 @@ public String getValue() { } @Override - public KeywordValidator newValidator(SchemaLocation schemaLocation, NodePath evaluationPath, + public KeywordValidator newValidator(SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, SchemaContext schemaContext) throws SchemaException, Exception { - return new EqualsValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, this, schemaContext); + return new EqualsValidator(schemaLocation, schemaNode, parentSchema, this, schemaContext); } } diff --git a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java index 452e97afd..22f8d33d7 100644 --- a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java +++ b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java @@ -135,8 +135,8 @@ void annotation() { + "}"; OutputUnit outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { executionContext.executionConfig(executionConfig -> executionConfig - .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); - }); + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); Set patternProperties = (Set) outputUnit.getAnnotations().get("patternProperties"); assertTrue(patternProperties.isEmpty()); @@ -146,12 +146,12 @@ void annotation() { + "}"; outputUnit = schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> { executionContext.executionConfig(executionConfig -> executionConfig - .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); - }); + .annotationCollectionEnabled(true).annotationCollectionFilter(keyword -> true)); + }); patternProperties = (Set) outputUnit.getAnnotations().get("patternProperties"); Set all = new HashSet<>(); all.add("valid_array"); all.add("valid_string"); - assertTrue(patternProperties.containsAll(patternProperties)); + assertTrue(patternProperties.containsAll(all)); } } diff --git a/src/test/java/com/networknt/schema/PropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PropertiesValidatorTest.java deleted file mode 100644 index 035081c0a..000000000 --- a/src/test/java/com/networknt/schema/PropertiesValidatorTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.networknt.schema; - -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.walk.ApplyDefaultsStrategy; -import com.networknt.schema.walk.WalkConfig; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -/** - * Created by josejulio on 25/04/22. - */ -class PropertiesValidatorTest extends BaseJsonSchemaValidatorTest { - - @Test - void testDoesNotThrowWhenApplyingDefaultPropertiesToNonObjects() throws Exception { - Assertions.assertDoesNotThrow(() -> { - WalkConfig walkConfig = WalkConfig.builder().applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); - SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); - Schema schema = factory.getSchema("{\"type\":\"object\",\"properties\":{\"foo\":{\"type\":\"object\", \"properties\": {} },\"i-have-default\":{\"type\":\"string\",\"default\":\"foo\"}}}"); - JsonNode node = getJsonNodeFromStringContent("{\"foo\": \"bar\"}"); - Result result = schema.walk(node, true, executionContext -> executionContext.setWalkConfig(walkConfig)); - Assertions.assertEquals(result.getErrors().size(), 1); - }); - } -} diff --git a/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java b/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java index d4f9d373a..2b53ac17e 100644 --- a/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java +++ b/src/test/java/com/networknt/schema/RecursiveReferenceValidatorExceptionTest.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.keyword.RecursiveRefValidator; -import com.networknt.schema.path.NodePath; -import com.networknt.schema.path.PathType; import org.junit.jupiter.api.Test; @@ -31,7 +29,7 @@ void testInvalidRecursiveReference() { // Act and Assert assertThrows(SchemaException.class, () -> { - new RecursiveRefValidator(SchemaLocation.of(""), new NodePath(PathType.JSON_POINTER), schemaNode, null, schemaContext); + new RecursiveRefValidator(SchemaLocation.of(""), schemaNode, null, schemaContext); }); } diff --git a/src/test/java/com/networknt/schema/TypeFactoryTest.java b/src/test/java/com/networknt/schema/TypeFactoryTest.java index b25a9a6f3..33acd4c33 100755 --- a/src/test/java/com/networknt/schema/TypeFactoryTest.java +++ b/src/test/java/com/networknt/schema/TypeFactoryTest.java @@ -44,7 +44,7 @@ class TypeFactoryTest { @Test void testIntegralValuesWithJavaSemantics() { - SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().javaSemantics(true).build(); + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(true).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.INTEGER, getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig), @@ -59,7 +59,7 @@ void testIntegralValuesWithJavaSemantics() { @Test void testIntegralValuesWithoutJavaSemantics() { - SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().javaSemantics(false).build(); + SchemaRegistryConfig schemaValidatorsConfig = SchemaRegistryConfig.builder().losslessNarrowing(false).build(); for (String validValue : validIntegralValues) { assertSame(JsonType.NUMBER, getValueNodeType(DecimalNode.valueOf(new BigDecimal(validValue)), schemaValidatorsConfig), diff --git a/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java b/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java new file mode 100644 index 000000000..6cbbfc600 --- /dev/null +++ b/src/test/java/com/networknt/schema/keyword/PropertiesValidatorTest.java @@ -0,0 +1,123 @@ +package com.networknt.schema.keyword; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.BaseJsonSchemaValidatorTest; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Result; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.walk.ApplyDefaultsStrategy; +import com.networknt.schema.walk.KeywordWalkListenerRunner; +import com.networknt.schema.walk.PropertyWalkListenerRunner; +import com.networknt.schema.walk.WalkConfig; +import com.networknt.schema.walk.WalkEvent; +import com.networknt.schema.walk.WalkFlow; +import com.networknt.schema.walk.WalkListener; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Created by josejulio on 25/04/22. + */ +class PropertiesValidatorTest extends BaseJsonSchemaValidatorTest { + + @Test + void testDoesNotThrowWhenApplyingDefaultPropertiesToNonObjects() throws Exception { + Assertions.assertDoesNotThrow(() -> { + WalkConfig walkConfig = WalkConfig.builder().applyDefaultsStrategy(new ApplyDefaultsStrategy(true, true, true)).build(); + SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + Schema schema = factory.getSchema("{\"type\":\"object\",\"properties\":{\"foo\":{\"type\":\"object\", \"properties\": {} },\"i-have-default\":{\"type\":\"string\",\"default\":\"foo\"}}}"); + JsonNode node = getJsonNodeFromStringContent("{\"foo\": \"bar\"}"); + Result result = schema.walk(node, true, executionContext -> executionContext.setWalkConfig(walkConfig)); + Assertions.assertEquals(result.getErrors().size(), 1); + }); + } + + @Test + void evaluationPath() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012()); + String schemaData = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"productId\": {\n" + + " \"type\": \"integer\",\n" + + " \"minimum\": 1\n" + + " }\n" + + " }\n" + + "}"; + String instanceData = "{\n" + + " \"productId\": 0\n" + + "}"; + Schema schema = schemaRegistry.getSchema(schemaData, InputFormat.JSON); + List errors = schema.validate(instanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + assertEquals("/properties/productId/minimum", errors.get(0).getEvaluationPath().toString()); + assertEquals("#/properties/productId/minimum", errors.get(0).getSchemaLocation().toString()); + assertEquals("minimum", errors.get(0).getKeyword()); + } + + @Test + void evaluationPathWalk() { + PropertyWalkListenerRunner propertyWalkListenerRunner = PropertyWalkListenerRunner.builder() + .propertyWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + + KeywordWalkListenerRunner keywordWalkListenerRunner = KeywordWalkListenerRunner.builder() + .keywordWalkListener(new WalkListener() { + @Override + public WalkFlow onWalkStart(WalkEvent walkEvent) { + return WalkFlow.CONTINUE; + } + @Override + public void onWalkEnd(WalkEvent walkEvent, List errors) { + } + }).build(); + + + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft202012()); + String schemaData = "{\n" + + " \"type\": \"object\",\n" + + " \"properties\": {\n" + + " \"productId\": {\n" + + " \"type\": \"integer\",\n" + + " \"minimum\": 1\n" + + " }\n" + + " },\n" + + " \"additionalProperties\": {\n" + + " \"type\": \"integer\"\n" + + " }\n" + + "}"; + String instanceData = "{\n" + + " \"productId\": 0,\n" + + " \"product\": \"hello\"\n" + + "}"; + Schema schema = schemaRegistry.getSchema(schemaData, InputFormat.JSON); + Result result = schema.walk(instanceData, InputFormat.JSON, true, + executionContext -> executionContext + .walkConfig(walkConfig -> walkConfig.propertyWalkListenerRunner(propertyWalkListenerRunner) + .keywordWalkListenerRunner(keywordWalkListenerRunner))); + List errors = result.getErrors(); + assertEquals(2, errors.size()); + assertEquals("/properties/productId/minimum", errors.get(0).getEvaluationPath().toString()); + assertEquals("#/properties/productId/minimum", errors.get(0).getSchemaLocation().toString()); + assertEquals("minimum", errors.get(0).getKeyword()); + assertEquals("/additionalProperties/type", errors.get(1).getEvaluationPath().toString()); + assertEquals("#/additionalProperties/type", errors.get(1).getSchemaLocation().toString()); + assertEquals("type", errors.get(1).getKeyword()); + } +} diff --git a/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java b/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java index 6e086c549..f0bce7bc1 100644 --- a/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java +++ b/src/test/java/com/networknt/schema/keyword/PropertyDependenciesValidatorTest.java @@ -18,7 +18,7 @@ */ public class PropertyDependenciesValidatorTest { @Test - void basicTest() { + void evaluationPath() { Dialect dialect = Dialect.builder(Dialects.getDraft202012()).keyword(KeywordType.PROPERTY_DEPENDENCIES).build(); SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(dialect); String schemaData = "{\r\n" diff --git a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java index dbe246c40..59ee56140 100644 --- a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java +++ b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java @@ -76,8 +76,8 @@ void jsonPointerWithNumberInFragment() { "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/responses/200/content/application~1json/schema") ); assertNotNull(schema); - assertEquals("$.paths['/pet'].post.responses['200'].content['application/json'].schema", - schema.getEvaluationPath().toString()); + //assertEquals("$.paths['/pet'].post.responses['200'].content['application/json'].schema", + // schema.getEvaluationPath().toString()); } /** diff --git a/src/test/java/com/networknt/schema/path/EvaluationPathTest.java b/src/test/java/com/networknt/schema/path/EvaluationPathTest.java new file mode 100644 index 000000000..ba8e862ce --- /dev/null +++ b/src/test/java/com/networknt/schema/path/EvaluationPathTest.java @@ -0,0 +1,88 @@ +package com.networknt.schema.path; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import com.networknt.schema.dialect.Dialects; + +/** + * Tests for evaluation path. + */ +public class EvaluationPathTest { + @Test + void baseUriChange() { + String schemaData = "[\r\n" + + " {\r\n" + + " \"schema\": {\r\n" + + " \"id\": \"http://localhost:1234/\",\r\n" + + " \"items\": {\r\n" + + " \"id\": \"baseUriChange/\",\r\n" + + " \"items\": {\"$ref\": \"folderInteger.json\"}\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "]"; + + String folderIntegerSchemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + + Map schemas = new HashMap<>(); + schemas.put("http://www.example.org/refRemote.json", schemaData); + schemas.put("http://localhost:1234/baseUriChange/folderInteger.json", folderIntegerSchemaData); + + String instanceData = "[[1,2,3,4,\"5\"]]"; + + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.schemas(schemas)); + Schema schemaWithIdFromUri = schemaRegistry.getSchema(SchemaLocation.of("http://www.example.org/refRemote.json#/0/schema")); + assertEquals("http://localhost:1234/#", schemaWithIdFromUri.getSchemaLocation().toString()); + List errors = schemaWithIdFromUri.validate(instanceData, InputFormat.JSON); + assertEquals(1, errors.size()); + // Previously the evaluation path was part of the state of the schema so the initial path might not be correct as it matches the schema location fragment + // assertEquals("/0/schema/items/items/$ref/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("/items/items/$ref/type", errors.get(0).getEvaluationPath().toString()); + assertEquals("http://localhost:1234/baseUriChange/folderInteger.json#/type", errors.get(0).getSchemaLocation().toString()); + assertEquals("/0/4", errors.get(0).getInstanceLocation().toString()); + assertEquals("type", errors.get(0).getKeyword()); + } + + @Test + void openapi() { + SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getOpenApi30()); + Schema schema = schemaRegistry.getSchema(SchemaLocation.of( + "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/requestBody/content/application~1json/schema")); + String invalid = "{\r\n" + + " \"petType\": \"dog\",\r\n" + + " \"meow\": \"meeeooow\"\r\n" + + "}"; + + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/requestBody/content/application~1json/schema", schema.getSchemaLocation().toString()); + List errors = schema.validate(invalid, InputFormat.JSON); + assertEquals(2, errors.size()); + assertEquals("oneOf", errors.get(0).getKeyword()); + // Previously the evaluation path was part of the state of the schema so the initial path might not be correct as it matches the schema location fragment + //assertEquals("/paths/~1pet/post/requestBody/content/application~1json/schema/$ref/oneOf", errors.get(0).getEvaluationPath().toString()); + assertEquals("/$ref/oneOf", errors.get(0).getEvaluationPath().toString()); + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/components/schemas/PetRequest/oneOf", errors.get(0).getSchemaLocation().toString()); + assertEquals("", errors.get(0).getInstanceLocation().toString()); + assertEquals("required", errors.get(1).getKeyword()); + assertEquals("bark", errors.get(1).getProperty()); + //assertEquals("/paths/~1pet/post/requestBody/content/application~1json/schema/$ref/oneOf/1/$ref/allOf/1/required", errors.get(1).getEvaluationPath().toString()); + assertEquals("/$ref/oneOf/1/$ref/allOf/1/required", errors.get(1).getEvaluationPath().toString()); + assertEquals("classpath:schema/oas/3.0/petstore.yaml#/components/schemas/Dog/allOf/1/required", errors.get(1).getSchemaLocation().toString()); + assertEquals("", errors.get(1).getInstanceLocation().toString()); + } + +} diff --git a/src/test/java/com/networknt/schema/walk/WalkListenerTest.java b/src/test/java/com/networknt/schema/walk/WalkListenerTest.java index c3efecda7..dc8a82e69 100644 --- a/src/test/java/com/networknt/schema/walk/WalkListenerTest.java +++ b/src/test/java/com/networknt/schema/walk/WalkListenerTest.java @@ -125,17 +125,17 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { 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()); + //assertEquals("/properties", propertyKeywords.get(0).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).getValidator().getEvaluationPath().toString()); - assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getSchema().getEvaluationPath() - .append(propertyKeywords.get(1).getKeyword()).toString()); + //assertEquals("/properties/tags/items/$ref/properties", + // propertyKeywords.get(1).getValidator().getEvaluationPath().toString()); + //assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(1).getEvaluationPath() + // .append(propertyKeywords.get(1).getKeyword()).toString()); assertEquals("/tags/1", propertyKeywords.get(2).getInstanceLocation().toString()); - assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getSchema().getEvaluationPath() - .append(propertyKeywords.get(2).getKeyword()).toString()); + //assertEquals("/properties/tags/items/$ref/properties", propertyKeywords.get(2).getEvaluationPath() + // .append(propertyKeywords.get(2).getKeyword()).toString()); assertEquals("link", propertyKeywords.get(2).getInstanceNode().get("name").asText()); } @@ -210,23 +210,23 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { assertEquals("properties", properties.get(0).getValidator().getKeyword()); assertEquals("/tags", properties.get(0).getInstanceLocation().toString()); - assertEquals("/properties/tags", properties.get(0).getSchema().getEvaluationPath().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).getSchema().getEvaluationPath().toString()); + 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).getSchema().getEvaluationPath().toString()); + 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).getSchema().getEvaluationPath().toString()); + 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).getSchema().getEvaluationPath().toString()); + assertEquals("/properties/tags/items/$ref/properties/description", properties.get(4).getEvaluationPath().toString()); } @Test @@ -300,10 +300,10 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { assertInstanceOf(ItemsLegacyValidator.class, items.get(0).getValidator()); assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); - assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); + assertEquals("/properties/tags/items", items.get(0).getEvaluationPath().toString()); assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); - assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); + assertEquals("/properties/tags/items", items.get(1).getEvaluationPath().toString()); } @Test @@ -376,10 +376,10 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { assertInstanceOf(ItemsValidator.class, items.get(0).getValidator()); assertEquals("/tags/0", items.get(0).getInstanceLocation().toString()); - assertEquals("/properties/tags/items", items.get(0).getSchema().getEvaluationPath().toString()); + assertEquals("/properties/tags/items", items.get(0).getEvaluationPath().toString()); assertEquals("/tags/1", items.get(1).getInstanceLocation().toString()); - assertEquals("/properties/tags/items", items.get(1).getSchema().getEvaluationPath().toString()); + assertEquals("/properties/tags/items", items.get(1).getEvaluationPath().toString()); } @Test @@ -431,115 +431,115 @@ public void onWalkEnd(WalkEvent walkEvent, List errors) { assertEquals(28, propertyKeywords.size()); assertEquals("", propertyKeywords.get(0).getInstanceLocation().toString()); - assertEquals("/properties", propertyKeywords.get(0).getSchema().getEvaluationPath().append(propertyKeywords.get(0).getKeyword()).toString()); + assertEquals("/properties", propertyKeywords.get(0).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).getSchema().getEvaluationPath().append(propertyKeywords.get(1).getKeyword()).toString()); + assertEquals("/allOf/0/$ref/properties", propertyKeywords.get(1).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).getSchema().getEvaluationPath().append(propertyKeywords.get(2).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties", propertyKeywords.get(2).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).getSchema().getEvaluationPath().append(propertyKeywords.get(3).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(3).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).getSchema().getEvaluationPath().append(propertyKeywords.get(4).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(4).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).getSchema().getEvaluationPath().append(propertyKeywords.get(5).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(5).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).getSchema().getEvaluationPath().append(propertyKeywords.get(6).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(6).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).getSchema().getEvaluationPath().append(propertyKeywords.get(7).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(7).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).getSchema().getEvaluationPath().append(propertyKeywords.get(8).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(8).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).getSchema().getEvaluationPath().append(propertyKeywords.get(9).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(9).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).getSchema().getEvaluationPath().append(propertyKeywords.get(10).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(10).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).getSchema().getEvaluationPath().append(propertyKeywords.get(11).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(11).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).getSchema().getEvaluationPath().append(propertyKeywords.get(12).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(12).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).getSchema().getEvaluationPath().append(propertyKeywords.get(13).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(13).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).getSchema().getEvaluationPath().append(propertyKeywords.get(14).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(14).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).getSchema().getEvaluationPath().append(propertyKeywords.get(15).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(15).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).getSchema().getEvaluationPath().append(propertyKeywords.get(16).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(16).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).getSchema().getEvaluationPath().append(propertyKeywords.get(17).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/properties", propertyKeywords.get(17).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).getSchema().getEvaluationPath().append(propertyKeywords.get(18).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/0/$ref/properties", propertyKeywords.get(18).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).getSchema().getEvaluationPath().append(propertyKeywords.get(19).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/1/$ref/properties", propertyKeywords.get(19).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).getSchema().getEvaluationPath().append(propertyKeywords.get(20).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/2/$ref/properties", propertyKeywords.get(20).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).getSchema().getEvaluationPath().append(propertyKeywords.get(21).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/3/$ref/properties", propertyKeywords.get(21).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).getSchema().getEvaluationPath().append(propertyKeywords.get(22).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/4/$ref/properties", propertyKeywords.get(22).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).getSchema().getEvaluationPath().append(propertyKeywords.get(23).getKeyword()).toString()); + assertEquals("/allOf/1/$ref/properties/properties/additionalProperties/$recursiveRef/allOf/5/$ref/properties", propertyKeywords.get(23).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).getSchema().getEvaluationPath().append(propertyKeywords.get(24).getKeyword()).toString()); + assertEquals("/allOf/2/$ref/properties", propertyKeywords.get(24).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).getSchema().getEvaluationPath().append(propertyKeywords.get(25).getKeyword()).toString()); + assertEquals("/allOf/3/$ref/properties", propertyKeywords.get(25).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).getSchema().getEvaluationPath().append(propertyKeywords.get(26).getKeyword()).toString()); + assertEquals("/allOf/4/$ref/properties", propertyKeywords.get(26).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).getSchema().getEvaluationPath().append(propertyKeywords.get(27).getKeyword()).toString()); + assertEquals("/allOf/5/$ref/properties", propertyKeywords.get(27).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()); } @@ -605,7 +605,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() || walkEvent.getInstanceNode().isNull()) { Schema schema = walkEvent.getSchema(); - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); if (schemaRef != null) { schema = schemaRef.getSchema(); } @@ -665,7 +665,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { if (walkEvent.getInstanceNode() == null || walkEvent.getInstanceNode().isMissingNode() || walkEvent.getInstanceNode().isNull()) { Schema schema = walkEvent.getSchema(); - SchemaRef schemaRef = SchemaRefs.from(schema); + SchemaRef schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); if (schemaRef != null) { schema = schemaRef.getSchema(); } @@ -740,7 +740,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { // Get the schema PropertiesValidator propertiesValidator = walkEvent.getValidator(); Schema propertySchema = propertiesValidator.getSchemas().get(requiredProperty); - SchemaRef schemaRef = SchemaRefs.from(propertySchema); + SchemaRef schemaRef = SchemaRefs.from(propertySchema, walkEvent.getExecutionContext()); if (schemaRef != null) { propertySchema = schemaRef.getSchema(); } @@ -805,7 +805,7 @@ public WalkFlow onWalkStart(WalkEvent walkEvent) { Schema schema = walkEvent.getSchema(); SchemaRef schemaRef = null; do { - schemaRef = SchemaRefs.from(schema); + schemaRef = SchemaRefs.from(schema, walkEvent.getExecutionContext()); if (schemaRef != null) { schema = schemaRef.getSchema(); }