From 00a643d4b0a60327d32395193d20871a09bbad2d Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Tue, 22 Dec 2020 18:01:51 +0530 Subject: [PATCH 01/14] Fixing walk issues for properties --- .../networknt/schema/PropertiesValidator.java | 92 +++++++++++-------- .../com/networknt/schema/ValidatorState.java | 13 +++ 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 7041702b3..5927fbb47 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -41,7 +41,7 @@ public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema pa .initialize()); } propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner( - config.getPropertyWalkListeners()); + config.getPropertyWalkListeners()); } public Set validate(JsonNode node, JsonNode rootNode, String at) { @@ -64,15 +64,20 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (propertyNode != null) { // check whether this is a complex validator. save the state boolean isComplex = state.isComplexValidator(); - // if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated + // if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated if (state.isComplexValidator()) { state.setMatchedNode(true); } - // reset the complex validator for child element validation, and reset it after the return from the recursive call + // reset the complex validator for child element validation, and reset it after the return from the recursive call state.setComplexValidator(false); - - //validate the child element(s) - errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); + // + if (!state.isWalkEnabled()) { + //validate the child element(s) + errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); + } else { + // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. + walkSchema(entry, node, rootNode, at, state.isWalkEnabled(), errors); + } // reset the complex flag to the original value before the recursive call state.setComplexValidator(isComplex); @@ -86,7 +91,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set requiredErrors = getParentSchema().getRequiredValidator().validate(node, rootNode, at); if (!requiredErrors.isEmpty()) { - // the node was mandatory, decide which behavior to employ when validator has not matched + // the node was mandatory, decide which behavior to employ when validator has not matched if (state.isComplexValidator()) { // this was a complex validator (ex oneOf) and the node has not been matched state.setMatchedNode(false); @@ -101,37 +106,48 @@ public Set validate(JsonNode node, JsonNode rootNode, String return Collections.unmodifiableSet(errors); } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } else { - boolean executeWalk = true; - for (Map.Entry entry : schemas.entrySet()) { - JsonSchema propertySchema = entry.getValue(); - JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); - executeWalk = propertyWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), - propertyNode, rootNode, at + "." + entry.getKey(), propertySchema.getSchemaPath(), - propertySchema.getSchemaNode(), propertySchema.getParentSchema(), - validationContext.getJsonSchemaFactory()); - if (executeWalk) { - validationMessages.addAll(propertySchema.walk(propertyNode, rootNode, at + "." + entry.getKey(), - shouldValidateSchema)); - } - propertyWalkListenerRunner.runPostWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, - rootNode, at + "." + entry.getKey(), propertySchema.getSchemaPath(), - propertySchema.getSchemaNode(), propertySchema.getParentSchema(), - validationContext.getJsonSchemaFactory(), validationMessages); - } - } - return validationMessages; - } - - public Map getSchemas() { - return schemas; - } + @Override + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + HashSet validationMessages = new LinkedHashSet(); + if (shouldValidateSchema) { + // get the Validator state object storing validation data + ValidatorState state = validatorState.get(); + if (state == null) { + // if one has not been created, instantiate one + state = new ValidatorState(); + state.setWalkEnabled(true); + validatorState.set(state); + } + validationMessages.addAll(validate(node, rootNode, at)); + } else { + for (Map.Entry entry : schemas.entrySet()) { + walkSchema(entry, node, rootNode, at, shouldValidateSchema, validationMessages); + } + } + return validationMessages; + } + + private void walkSchema(Map.Entry entry, JsonNode node, JsonNode rootNode, String at, + boolean shouldValidateSchema, Set validationMessages) { + JsonSchema propertySchema = entry.getValue(); + JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); + boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), + propertyNode, rootNode, at + "." + entry.getKey(), propertySchema.getSchemaPath(), + propertySchema.getSchemaNode(), propertySchema.getParentSchema(), + validationContext.getJsonSchemaFactory()); + if (executeWalk) { + validationMessages.addAll( + propertySchema.walk(propertyNode, rootNode, at + "." + entry.getKey(), shouldValidateSchema)); + } + propertyWalkListenerRunner.runPostWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), propertyNode, rootNode, + at + "." + entry.getKey(), propertySchema.getSchemaPath(), propertySchema.getSchemaNode(), + propertySchema.getParentSchema(), validationContext.getJsonSchemaFactory(), validationMessages); + + } + + public Map getSchemas() { + return schemas; + } } diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java index 8770cd6ad..422181698 100644 --- a/src/main/java/com/networknt/schema/ValidatorState.java +++ b/src/main/java/com/networknt/schema/ValidatorState.java @@ -29,6 +29,11 @@ public class ValidatorState { */ private boolean isComplexValidator = false; + /** + * Flag to check if walking is enabled while validation is happening. + */ + private boolean isWalkEnabled = false; + public void setMatchedNode(boolean matchedNode) { this.matchedNode = matchedNode; } @@ -45,4 +50,12 @@ public void setComplexValidator(boolean isComplexValidator) { this.isComplexValidator = isComplexValidator; } + public boolean isWalkEnabled() { + return isWalkEnabled; + } + + public void setWalkEnabled(boolean isWalkEnabled) { + this.isWalkEnabled = isWalkEnabled; + } + } From 161f59cab29d9e50e866b99774fcb4eb6da93ded Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Thu, 24 Dec 2020 21:39:24 +0530 Subject: [PATCH 02/14] Correcting walk changes for few validators --- .../schema/AdditionalPropertiesValidator.java | 44 +++---------------- .../com/networknt/schema/AllOfValidator.java | 4 -- .../java/com/networknt/schema/JsonSchema.java | 19 +++++++- .../com/networknt/schema/NotValidator.java | 7 +-- .../com/networknt/schema/OneOfValidator.java | 28 ++++++++++-- .../networknt/schema/PatternValidator.java | 7 +-- .../networknt/schema/PropertiesValidator.java | 10 +---- .../com/networknt/schema/RefValidator.java | 9 +--- .../schema/walk/JsonSchemaWalker.java | 4 +- 9 files changed, 55 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java index 278b3b5dc..9906f2623 100644 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java @@ -94,49 +94,17 @@ public Set validate(JsonNode node, JsonNode rootNode, String errors.add(buildValidationMessage(at, pname)); } else { if (additionalPropertiesSchema != null) { - errors.addAll(additionalPropertiesSchema.validate(node.get(pname), rootNode, at + "." + pname)); + ValidatorState state = validatorState.get(); + if (state != null && state.isWalkEnabled()) { + errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isWalkEnabled())); + } else { + errors.addAll(additionalPropertiesSchema.validate(node.get(pname), rootNode, at + "." + pname)); + } } } } } return Collections.unmodifiableSet(errors); } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (!node.isObject()) { - return validationMessages; - } - // Validate schema if required. - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } - for (Iterator it = node.fieldNames(); it.hasNext();) { - String pname = it.next(); - // skip the context items - if (pname.startsWith("#")) { - continue; - } - boolean handledByPatternProperties = false; - for (Pattern pattern : patternProperties) { - Matcher m = pattern.matcher(pname); - if (m.find()) { - handledByPatternProperties = true; - break; - } - } - if (!allowedProperties.contains(pname) && !handledByPatternProperties) { - if (allowAdditionalProperties) { - if (additionalPropertiesSchema != null) { - validationMessages.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, - shouldValidateSchema)); - } - } - } - } - - return validationMessages; - } } diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java index 312f9ae16..af9b3145a 100644 --- a/src/main/java/com/networknt/schema/AllOfValidator.java +++ b/src/main/java/com/networknt/schema/AllOfValidator.java @@ -53,10 +53,6 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, Set validationMessages = new LinkedHashSet(); for (JsonSchema schema : schemas) { - // Check if validation is needed. - if (shouldValidateSchema) { - validationMessages.addAll(schema.validate(node, rootNode, at)); - } // Walk through the schema validationMessages.addAll(schema.walk(node, rootNode, at, shouldValidateSchema)); } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 0850e46d3..c3830dad2 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -275,7 +275,10 @@ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { // Create the collector context object. CollectorContext collectorContext = new CollectorContext(); // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); + ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); + // Set the walkEnabled flag in internal validator state. + setValidatorState(shouldValidateSchema); + // Walk through the schema. Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); // Load all the data from collectors into the context. collectorContext.loadCollectors(); @@ -284,7 +287,8 @@ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { return validationResult; } - @Override + + @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { Set validationMessages = new LinkedHashSet(); // Walk through all the JSONWalker's. @@ -309,6 +313,17 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, /************************ END OF WALK METHODS **********************************/ + private void setValidatorState(boolean shouldValidateSchema) { + // Get the Validator state object storing validation data + ValidatorState state = validatorState.get(); + if (state == null) { + // if one has not been created, instantiate one + state = new ValidatorState(); + state.setWalkEnabled(shouldValidateSchema); + validatorState.set(state); + } + } + @Override public String toString() { return "\"" + getSchemaPath() + "\" : " + getSchemaNode().toString(); diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java index 8dbbda234..2f79bcda5 100644 --- a/src/main/java/com/networknt/schema/NotValidator.java +++ b/src/main/java/com/networknt/schema/NotValidator.java @@ -49,12 +49,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } - validationMessages.addAll(schema.walk(node, rootNode, at, shouldValidateSchema)); - return validationMessages; + return schema.walk(node, rootNode, at, shouldValidateSchema); } } diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index 0ba783229..2dc59d439 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -129,10 +129,14 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); + ValidatorState state = validatorState.get(); + if (state == null) { + state = new ValidatorState(); + validatorState.set(state); + } // this is a complex validator, we set the flag to true - ValidatorState state = new ValidatorState(); state.setComplexValidator(true); - validatorState.set(state); + int numberOfValidSchema = 0; Set errors = new LinkedHashSet(); @@ -145,6 +149,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String // } for (ShortcutValidator validator : schemas) { + Set schemaErrors = null; if (!validator.allConstantsMatch(node)) { // take a shortcut: if there is any constant that does not match, // we can bail out of the validation @@ -153,7 +158,11 @@ public Set validate(JsonNode node, JsonNode rootNode, String // get the current validator JsonSchema schema = validator.schema; - Set schemaErrors = schema.validate(node, rootNode, at); + if (!state.isWalkEnabled()) { + schemaErrors = schema.validate(node, rootNode, at); + } else { + schemaErrors = schema.walk(node, rootNode, at, state.isWalkEnabled()); + } // check if any validation errors have occurred if (schemaErrors.isEmpty()) { @@ -185,4 +194,17 @@ public List getChildSchemas() { return childJsonSchemas; } + @Override + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + HashSet validationMessages = new LinkedHashSet(); + if (shouldValidateSchema) { + validationMessages.addAll(validate(node, rootNode, at)); + } else { + for (ShortcutValidator validator : schemas) { + validator.schema.walk(node, rootNode, at , shouldValidateSchema); + } + } + return validationMessages; + } + } diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java index 6fe4c22a9..1a1569789 100644 --- a/src/main/java/com/networknt/schema/PatternValidator.java +++ b/src/main/java/com/networknt/schema/PatternValidator.java @@ -55,12 +55,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } - validationMessages.addAll(delegate.walk(node, rootNode, at, shouldValidateSchema)); - return validationMessages; + return delegate.walk(node, rootNode, at, shouldValidateSchema); } private static class PatternValidatorJava extends BaseJsonValidator implements JsonValidator { diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 5927fbb47..1b519fbbd 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -70,7 +70,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String } // reset the complex validator for child element validation, and reset it after the return from the recursive call state.setComplexValidator(false); - // + if (!state.isWalkEnabled()) { //validate the child element(s) errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); @@ -111,14 +111,6 @@ public Set validate(JsonNode node, JsonNode rootNode, String public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { HashSet validationMessages = new LinkedHashSet(); if (shouldValidateSchema) { - // get the Validator state object storing validation data - ValidatorState state = validatorState.get(); - if (state == null) { - // if one has not been created, instantiate one - state = new ValidatorState(); - state.setWalkEnabled(true); - validatorState.set(state); - } validationMessages.addAll(validate(node, rootNode, at)); } else { for (Map.Entry entry : schemas.entrySet()) { diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java index 4df7c7d90..1a6813412 100644 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ b/src/main/java/com/networknt/schema/RefValidator.java @@ -144,15 +144,10 @@ public Set validate(JsonNode node, JsonNode rootNode, String @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - - if (shouldValidateSchema) { - validationMessages.addAll(validate(node, rootNode, at)); - } if (schema != null) { - validationMessages.addAll(schema.walk(node, rootNode, at, shouldValidateSchema)); + return schema.walk(node, rootNode, at, shouldValidateSchema); } - return validationMessages; + return Collections.emptySet(); } public JsonSchemaRef getSchemaRef() { diff --git a/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java b/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java index 79be872ee..bb940b222 100644 --- a/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java +++ b/src/main/java/com/networknt/schema/walk/JsonSchemaWalker.java @@ -15,9 +15,9 @@ public interface JsonSchemaWalker { * the validation if {@code shouldValidateSchema} is set to true.
*
* {@link BaseJsonValidator#walk(JsonNode, JsonNode, String, boolean)} provides - * a default implementation of this method. However keywords that parse + * a default implementation of this method. However validators that parse * sub-schemas should override this method to call walk method on those - * subschemas. + * sub-schemas. * * @param node JsonNode * @param rootNode JsonNode From 32e8ab875b934ee7bd13b0b185955382ca6b2892 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Mon, 28 Dec 2020 18:07:13 +0530 Subject: [PATCH 03/14] adding item walk listener --- .../com/networknt/schema/ItemsValidator.java | 137 ++++++++++-------- .../schema/SchemaValidatorsConfig.java | 16 +- .../walk/DefaultItemWalkListenerRunner.java | 37 +++++ 3 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index c708762d8..ed5fb690d 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -17,6 +17,9 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.walk.DefaultItemWalkListenerRunner; +import com.networknt.schema.walk.WalkListenerRunner; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,17 +33,21 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator { private List tupleSchema; private boolean additionalItems = true; private JsonSchema additionalSchema; + private WalkListenerRunner arrayItemWalkListenerRunner; + private ValidationContext validationContext; - public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { + public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, + ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext); if (schemaNode.isObject() || schemaNode.isBoolean()) { - schema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, parentSchema) - .initialize(); + schema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, + parentSchema).initialize(); } else { tupleSchema = new ArrayList(); for (JsonNode s : schemaNode) { - tupleSchema.add(new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema) - .initialize()); + tupleSchema.add( + new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema) + .initialize()); } JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS); @@ -49,10 +56,13 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS additionalItems = addItemNode.asBoolean(); } else if (addItemNode.isObject()) { additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUri(), addItemNode) - .initialize(); + .initialize(); } } } + arrayItemWalkListenerRunner = new DefaultItemWalkListenerRunner(config.getArrayItemWalkListeners()); + + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -100,57 +110,64 @@ private void doValidate(Set errors, int i, JsonNode node, Jso } } } - - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - HashSet validationMessages = new LinkedHashSet(); - if (node != null && node.isArray()) { - int i = 0; - for (JsonNode n : node) { - doWalk(validationMessages, i, n, rootNode, at, shouldValidateSchema); - i++; - } - } else { - doWalk(validationMessages, 0, node, rootNode, at, shouldValidateSchema); - } - return validationMessages; - } - - private void doWalk(HashSet validationMessages, int i, JsonNode node, JsonNode rootNode, - String at, boolean shouldValidateSchema) { - if (schema != null) { - if (shouldValidateSchema) { - validationMessages.addAll(schema.validate(node, rootNode, at)); - } - // Walk the schema. - validationMessages.addAll(schema.walk(node, rootNode, at + "[" + i + "]", shouldValidateSchema)); - } - - if (tupleSchema != null) { - if (i < tupleSchema.size()) { - if (shouldValidateSchema) { - validationMessages.addAll(tupleSchema.get(i).validate(node, rootNode, at)); - } - // walk tuple schema - validationMessages.addAll(tupleSchema.get(i).walk(node, rootNode, at + "[" + i + "]", shouldValidateSchema)); - } else { - if (additionalSchema != null) { - if (shouldValidateSchema) { - validationMessages.addAll(additionalSchema.validate(node, rootNode, at)); - } - // walk additional item schema - validationMessages.addAll(additionalSchema.walk(node, rootNode, at + "[" + i + "]", shouldValidateSchema)); - } - } - } - } - - public List getTupleSchema() { - return this.tupleSchema; - } - - public JsonSchema getSchema() { - return schema; - } - -} + + @Override + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + HashSet validationMessages = new LinkedHashSet(); + if (node != null && node.isArray()) { + int i = 0; + for (JsonNode n : node) { + doWalk(validationMessages, i, n, rootNode, at, shouldValidateSchema); + i++; + } + } else { + doWalk(validationMessages, 0, node, rootNode, at, shouldValidateSchema); + } + return validationMessages; + } + + private void doWalk(HashSet validationMessages, int i, JsonNode node, JsonNode rootNode, + String at, boolean shouldValidateSchema) { + if (schema != null) { + // Walk the schema. + walkSchema(schema, node, rootNode, at + "[" + i + "]", shouldValidateSchema, validationMessages); + } + + if (tupleSchema != null) { + if (i < tupleSchema.size()) { + // walk tuple schema + walkSchema(tupleSchema.get(i), node, rootNode, at + "[" + i + "]", shouldValidateSchema, + validationMessages); + } else { + if (additionalSchema != null) { + // walk additional item schema + walkSchema(additionalSchema, node, rootNode, at + "[" + i + "]", shouldValidateSchema, + validationMessages); + } + } + } + } + + private void walkSchema(JsonSchema walkSchema, JsonNode node, JsonNode rootNode, String at, + boolean shouldValidateSchema, Set validationMessages) { + boolean executeWalk = arrayItemWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.ITEMS.getValue(), node, + rootNode, at, walkSchema.getSchemaPath(), walkSchema.getSchemaNode(), walkSchema.getParentSchema(), + validationContext.getJsonSchemaFactory()); + if (executeWalk) { + validationMessages.addAll(walkSchema.walk(node, rootNode, at, shouldValidateSchema)); + } + arrayItemWalkListenerRunner.runPostWalkListeners(ValidatorTypeCode.ITEMS.getValue(), node, rootNode, at, + walkSchema.getSchemaPath(), walkSchema.getSchemaNode(), walkSchema.getParentSchema(), + validationContext.getJsonSchemaFactory(), validationMessages); + + } + + public List getTupleSchema() { + return this.tupleSchema; + } + + public JsonSchema getSchema() { + return schema; + } + +} \ No newline at end of file diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java index 8fbea105b..3f1f182cc 100644 --- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java +++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java @@ -64,7 +64,9 @@ public class SchemaValidatorsConfig { private final Map> keywordWalkListenersMap = new HashMap>(); - private final List propertyWalkListeners = new ArrayList(); + private final List propertyWalkListeners = new ArrayList(); + + private final List itemWalkListeners = new ArrayList(); public boolean isTypeLoose() { return typeLoose; @@ -157,6 +159,14 @@ public void addPropertyWalkListeners(List propertyWalkLi public void addPropertyWalkListener(JsonSchemaWalkListener propertyWalkListener) { this.propertyWalkListeners.add(propertyWalkListener); + } + + public void addItemWalkListener(JsonSchemaWalkListener itemWalkListener) { + this.itemWalkListeners.add(itemWalkListener); + } + + public void addItemWalkListeners(List itemWalkListeners) { + this.itemWalkListeners.addAll(itemWalkListeners); } public List getPropertyWalkListeners() { @@ -165,6 +175,10 @@ public List getPropertyWalkListeners() { public Map> getKeywordWalkListenersMap() { return this.keywordWalkListenersMap; + } + + public List getArrayItemWalkListeners() { + return this.itemWalkListeners; } public SchemaValidatorsConfig() { diff --git a/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java new file mode 100644 index 000000000..bc5df0577 --- /dev/null +++ b/src/main/java/com/networknt/schema/walk/DefaultItemWalkListenerRunner.java @@ -0,0 +1,37 @@ +package com.networknt.schema.walk; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.ValidationMessage; + +import java.util.List; +import java.util.Set; + +public class DefaultItemWalkListenerRunner extends AbstractWalkListenerRunner { + + private List itemWalkListeners; + + public DefaultItemWalkListenerRunner(List itemWalkListeners) { + this.itemWalkListeners = itemWalkListeners; + } + + @Override + public boolean runPreWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, + String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, + JsonSchemaFactory currentJsonSchemaFactory) { + WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, + currentJsonSchemaFactory); + return runPreWalkListeners(itemWalkListeners, walkEvent); + } + + @Override + public void runPostWalkListeners(String keyWordPath, JsonNode node, JsonNode rootNode, String at, String schemaPath, + JsonNode schemaNode, JsonSchema parentSchema, JsonSchemaFactory currentJsonSchemaFactory, + Set validationMessages) { + WalkEvent walkEvent = constructWalkEvent(keyWordPath, node, rootNode, at, schemaPath, schemaNode, parentSchema, + currentJsonSchemaFactory); + runPostWalkListeners(itemWalkListeners, walkEvent, validationMessages); + } + +} \ No newline at end of file From 3e2e7cd015e5eb63e5d12d564fc2762b8d058bb4 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Wed, 13 Jan 2021 20:20:39 +0530 Subject: [PATCH 04/14] walk listener changes --- src/main/java/com/networknt/schema/JsonSchema.java | 8 ++++++-- .../com/networknt/schema/SchemaValidatorsConfig.java | 11 ++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index c3830dad2..34070aa6c 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -246,8 +246,12 @@ public ValidationResult validateAndCollect(JsonNode node) { */ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) { try { - // Create the collector context object. - CollectorContext collectorContext = new CollectorContext(); + CollectorContext collectorContext; + if(this.config !=null && this.config.getCollectorContext() != null){ + collectorContext = this.config.getCollectorContext(); + } else { + collectorContext = new CollectorContext(); + } // Set the collector context in thread info, this is unique for every thread. ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); Set errors = validate(jsonNode, rootNode, at); diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java index 3f1f182cc..9f91d1ada 100644 --- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java +++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java @@ -61,13 +61,15 @@ public class SchemaValidatorsConfig { // This is just a constant for listening to all Keywords. public static final String ALL_KEYWORD_WALK_LISTENER_KEY = "com.networknt.AllKeywordWalkListener"; - + private final Map> keywordWalkListenersMap = new HashMap>(); private final List propertyWalkListeners = new ArrayList(); private final List itemWalkListeners = new ArrayList(); + private CollectorContext collectorContext; + public boolean isTypeLoose() { return typeLoose; } @@ -183,5 +185,12 @@ public List getArrayItemWalkListeners() { public SchemaValidatorsConfig() { } + + public CollectorContext getCollectorContext() { + return collectorContext; + } + public void setCollectorContext(CollectorContext collectorContext) { + this.collectorContext = collectorContext; + } } From 51e45baea683ea330224f6f7593eace9e881eb9a Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Wed, 13 Jan 2021 21:06:51 +0530 Subject: [PATCH 05/14] walk listener changes --- .../schema/AdditionalPropertiesValidator.java | 2 +- .../java/com/networknt/schema/JsonSchema.java | 7 ++++--- .../java/com/networknt/schema/OneOfValidator.java | 2 +- .../com/networknt/schema/PropertiesValidator.java | 2 +- .../java/com/networknt/schema/ValidatorState.java | 15 ++++++++++++++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java index 9906f2623..5ec7181aa 100644 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java @@ -96,7 +96,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (additionalPropertiesSchema != null) { ValidatorState state = validatorState.get(); if (state != null && state.isWalkEnabled()) { - errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isWalkEnabled())); + errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabledWhileWalking())); } else { errors.addAll(additionalPropertiesSchema.validate(node.get(pname), rootNode, at + "." + pname)); } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 34070aa6c..63299b647 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -281,7 +281,7 @@ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { // Set the collector context in thread info, this is unique for every thread. ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); // Set the walkEnabled flag in internal validator state. - setValidatorState(shouldValidateSchema); + setValidatorState(true, shouldValidateSchema); // Walk through the schema. Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); // Load all the data from collectors into the context. @@ -317,13 +317,14 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, /************************ END OF WALK METHODS **********************************/ - private void setValidatorState(boolean shouldValidateSchema) { + private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSchema) { // Get the Validator state object storing validation data ValidatorState state = validatorState.get(); if (state == null) { // if one has not been created, instantiate one state = new ValidatorState(); - state.setWalkEnabled(shouldValidateSchema); + state.setWalkEnabled(isWalkEnabled); + state.setValidationEnabledWhileWalking(shouldValidateSchema); validatorState.set(state); } } diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index 2dc59d439..3a5dccb18 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -161,7 +161,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (!state.isWalkEnabled()) { schemaErrors = schema.validate(node, rootNode, at); } else { - schemaErrors = schema.walk(node, rootNode, at, state.isWalkEnabled()); + schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabledWhileWalking()); } // check if any validation errors have occurred diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 1b519fbbd..f7e19747f 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -76,7 +76,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); } else { // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. - walkSchema(entry, node, rootNode, at, state.isWalkEnabled(), errors); + walkSchema(entry, node, rootNode, at, state.isValidationEnabledWhileWalking(), errors); } // reset the complex flag to the original value before the recursive call diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java index 422181698..e3a80d859 100644 --- a/src/main/java/com/networknt/schema/ValidatorState.java +++ b/src/main/java/com/networknt/schema/ValidatorState.java @@ -30,10 +30,15 @@ public class ValidatorState { private boolean isComplexValidator = false; /** - * Flag to check if walking is enabled while validation is happening. + * Flag to check if walking is enabled. */ private boolean isWalkEnabled = false; + /** + * Flag to check if validation is enabled while walking. + */ + private boolean isValidationEnabledWhileWalking = false; + public void setMatchedNode(boolean matchedNode) { this.matchedNode = matchedNode; } @@ -58,4 +63,12 @@ public void setWalkEnabled(boolean isWalkEnabled) { this.isWalkEnabled = isWalkEnabled; } + public boolean isValidationEnabledWhileWalking() { + return isValidationEnabledWhileWalking; + } + + public void setValidationEnabledWhileWalking(boolean isValidationEnabledWhileWalking) { + this.isValidationEnabledWhileWalking = isValidationEnabledWhileWalking; + } + } From 9fdb3d022c0826a23227588fd7f50307a72efcb9 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Fri, 26 Feb 2021 21:27:28 +0530 Subject: [PATCH 06/14] merging changes from networknt --- CHANGELOG.md | 27 +++ README.md | 4 +- pom.xml | 6 +- .../schema/AdditionalPropertiesValidator.java | 3 +- .../com/networknt/schema/AllOfValidator.java | 3 +- .../com/networknt/schema/AnyOfValidator.java | 7 +- .../networknt/schema/BaseJsonValidator.java | 32 +-- .../networknt/schema/ContainsValidator.java | 3 +- .../schema/DependenciesValidator.java | 3 +- .../com/networknt/schema/IfValidator.java | 9 +- .../com/networknt/schema/ItemsValidator.java | 8 +- .../java/com/networknt/schema/JsonSchema.java | 14 +- .../networknt/schema/JsonSchemaFactory.java | 3 +- .../com/networknt/schema/NotValidator.java | 3 +- .../com/networknt/schema/OneOfValidator.java | 65 ++++-- .../schema/PatternPropertiesValidator.java | 3 +- .../networknt/schema/PropertiesValidator.java | 3 +- .../schema/PropertyNamesValidator.java | 18 +- .../com/networknt/schema/RefValidator.java | 3 +- .../networknt/schema/UnionTypeValidator.java | 3 +- .../networknt/schema/Issue366FailFast.java | 101 +++++++++ .../networknt/schema/Issue366FailSlow.java | 105 +++++++++ .../com/networknt/schema/Issue375Test.java | 61 ++++++ src/test/resources/data/issue366.json | 24 +++ src/test/resources/data/issue375.json | 10 + src/test/resources/draft2019-09/issue375.json | 28 +++ .../resources/draft2019-09/propertyNames.json | 29 +++ src/test/resources/draft4/oneOf.json | 201 +----------------- src/test/resources/draft6/propertyNames.json | 29 +++ src/test/resources/draft7/propertyNames.json | 29 +++ .../resources/schema/issue366_schema.json | 11 + 31 files changed, 562 insertions(+), 286 deletions(-) create mode 100644 src/test/java/com/networknt/schema/Issue366FailFast.java create mode 100644 src/test/java/com/networknt/schema/Issue366FailSlow.java create mode 100644 src/test/java/com/networknt/schema/Issue375Test.java create mode 100644 src/test/resources/data/issue366.json create mode 100644 src/test/resources/data/issue375.json create mode 100644 src/test/resources/draft2019-09/issue375.json create mode 100644 src/test/resources/schema/issue366_schema.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbbf8e3c..ad54c46bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +## 1.0.49 - 2021-02-17 + +### Added + +### Changed + +- fixes #375 PropertyNames to return validator value on error. Thanks @Eivyses +- fixes #335 Fixed parallel processing. @Thanks @mweber03 + +## 1.0.48 - 2021-02-04 + +### Added + +### Changed + +- fixes #326 pattern validation for propertyNames. @Thanks @LeifRilbeATG +- fixes #366 Fast fail issue with One Of Validator. Thanks @Krishna-capone + +## 1.0.47 - 2021-01-16 + +### Added + +### Changed + +- fixes #368 Fixing Walk Listeners Issues. @Thanks prashanthjos +- fixes #363 Date-time validation fails depending on local time zone. Thanks @ennoruijters + ## 1.0.46 - 2020-12-30 ### Added diff --git a/README.md b/README.md index e5a931847..9a147156b 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Maven: com.networknt json-schema-validator - 1.0.46 + 1.0.49 ``` @@ -90,7 +90,7 @@ Gradle: ``` dependencies { - compile(group: "com.networknt", name: "json-schema-validator", version: "1.0.46"); + compile(group: "com.networknt", name: "json-schema-validator", version: "1.0.49"); } ``` diff --git a/pom.xml b/pom.xml index f8bf03ef1..eeb3af44c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 com.networknt json-schema-validator - 1.0.46 + 1.0.49 bundle A json schema validator that supports draft v4, v6, v7 and v2019-09 https://github.com/networknt/json-schema-validator @@ -62,7 +62,7 @@ 1.6 1.8 UTF-8 - 2.10.4 + 2.12.1 1.7.30 3.7 2.1.31 @@ -70,7 +70,7 @@ 4.13.1 2.7.21 1.3 - 2.1.3.Final + 2.2.4.Final diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java index 5ec7181aa..f4f10cf02 100644 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java @@ -40,8 +40,7 @@ public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, Jso additionalPropertiesSchema = null; } else if (schemaNode.isObject()) { allowAdditionalProperties = true; - additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema) - .initialize(); + additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); } else { allowAdditionalProperties = false; additionalPropertiesSchema = null; diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java index af9b3145a..82b193dd6 100644 --- a/src/main/java/com/networknt/schema/AllOfValidator.java +++ b/src/main/java/com/networknt/schema/AllOfValidator.java @@ -31,8 +31,7 @@ public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext); int size = schemaNode.size(); for (int i = 0; i < size; i++) { - schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode.get(i), parentSchema) - .initialize()); + schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode.get(i), parentSchema)); } } diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java index 9ac11db51..5028e8201 100644 --- a/src/main/java/com/networknt/schema/AnyOfValidator.java +++ b/src/main/java/com/networknt/schema/AnyOfValidator.java @@ -31,8 +31,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext); int size = schemaNode.size(); for (int i = 0; i < size; i++) { - schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode.get(i), parentSchema) - .initialize()); + schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode.get(i), parentSchema)); } } @@ -43,8 +42,8 @@ public Set validate(JsonNode node, JsonNode rootNode, String String typeValidatorName = "anyOf/type"; for (JsonSchema schema : schemas) { - if (schema.validators.containsKey(typeValidatorName)) { - TypeValidator typeValidator = ((TypeValidator) schema.validators.get(typeValidatorName)); + if (schema.getValidators().containsKey(typeValidatorName)) { + TypeValidator typeValidator = ((TypeValidator) schema.getValidators().get(typeValidatorName)); //If schema has type validator and node type doesn't match with schemaType then ignore it //For union type, it is must to call TypeValidator if (typeValidator.getSchemaType() != JsonType.UNION && !typeValidator.equalsToSchemaType(node)) { diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java index 84607ed77..53d1c8fa3 100644 --- a/src/main/java/com/networknt/schema/BaseJsonValidator.java +++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java @@ -126,7 +126,7 @@ protected void parseErrorCode(String errorCodeKey) { protected ValidationMessage buildValidationMessage(String at, String... arguments) { final ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments); - if (failFast) { + if (failFast && !isPartOfOneOfMultipleType()) { throw new JsonSchemaException(message); } return message; @@ -149,18 +149,22 @@ protected String getNodeFieldType() { } return null; } - - /** - * This is default implementation of walk method. Its job is to call the - * validate method if shouldValidateSchema is enabled. - */ - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - if (shouldValidateSchema) { - validationMessages = validate(node, rootNode, at); - } - return validationMessages; - } + + /** + * This is default implementation of walk method. Its job is to call the + * validate method if shouldValidateSchema is enabled. + */ + @Override + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + Set validationMessages = new LinkedHashSet(); + if (shouldValidateSchema) { + validationMessages = validate(node, rootNode, at); + } + return validationMessages; + } + + protected boolean isPartOfOneOfMultipleType(){ + return parentSchema.schemaPath.equals(ValidatorTypeCode.ONE_OF.getValue()); + } } diff --git a/src/main/java/com/networknt/schema/ContainsValidator.java b/src/main/java/com/networknt/schema/ContainsValidator.java index dd8f7b6d2..73dc02bed 100644 --- a/src/main/java/com/networknt/schema/ContainsValidator.java +++ b/src/main/java/com/networknt/schema/ContainsValidator.java @@ -31,8 +31,7 @@ public class ContainsValidator extends BaseJsonValidator implements JsonValidato public ContainsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.CONTAINS, validationContext); if (schemaNode.isObject() || schemaNode.isBoolean()) { - schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema) - .initialize(); + schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); } parseErrorCode(getValidatorType().getErrorCodeKey()); diff --git a/src/main/java/com/networknt/schema/DependenciesValidator.java b/src/main/java/com/networknt/schema/DependenciesValidator.java index 3dd96444c..5f76203d2 100644 --- a/src/main/java/com/networknt/schema/DependenciesValidator.java +++ b/src/main/java/com/networknt/schema/DependenciesValidator.java @@ -44,8 +44,7 @@ public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema depsProps.add(pvalue.get(i).asText()); } } else if (pvalue.isObject() || pvalue.isBoolean()) { - schemaDeps.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema) - .initialize()); + schemaDeps.put(pname, new JsonSchema(validationContext, pname, parentSchema.getCurrentUri(), pvalue, parentSchema)); } } diff --git a/src/main/java/com/networknt/schema/IfValidator.java b/src/main/java/com/networknt/schema/IfValidator.java index 843016ff3..e9868b862 100644 --- a/src/main/java/com/networknt/schema/IfValidator.java +++ b/src/main/java/com/networknt/schema/IfValidator.java @@ -37,14 +37,11 @@ public IfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSche for (final String keyword : KEYWORDS) { final JsonNode node = schemaNode.get(keyword); if (keyword.equals("if")) { - ifSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema) - .initialize(); + ifSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); } else if (keyword.equals("then") && node != null) { - thenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema) - .initialize(); + thenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); } else if (keyword.equals("else") && node != null) { - elseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema) - .initialize(); + elseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema); } } } diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index ed5fb690d..05253dfd9 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -41,13 +41,12 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext); if (schemaNode.isObject() || schemaNode.isBoolean()) { schema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode, - parentSchema).initialize(); + parentSchema); } else { tupleSchema = new ArrayList(); for (JsonNode s : schemaNode) { tupleSchema.add( - new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema) - .initialize()); + new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema)); } JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS); @@ -55,8 +54,7 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS if (addItemNode.isBoolean()) { additionalItems = addItemNode.asBoolean(); } else if (addItemNode.isObject()) { - additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUri(), addItemNode) - .initialize(); + additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUri(), addItemNode); } } } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 63299b647..098c90fc0 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -36,7 +36,7 @@ */ public class JsonSchema extends BaseJsonValidator { private static final Pattern intPattern = Pattern.compile("^[0-9]+$"); - protected Map validators; + private Map validators; private final String idKeyword; private final ValidationContext validationContext; private WalkListenerRunner keywordWalkListenerRunner; @@ -80,11 +80,6 @@ private JsonSchema(ValidationContext validationContext, String schemaPath, URI c } } - JsonSchema initialize() { - this.validators = Collections.unmodifiableMap(this.read(getSchemaNode())); - return this; - } - private URI combineCurrentUriWithIds(URI currentUri, JsonNode schemaNode) { final String id = validationContext.resolveSchemaId(schemaNode); if (id == null) { @@ -225,7 +220,7 @@ private Map read(JsonNode schemaNode) { public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { Set errors = new LinkedHashSet(); - for (JsonValidator v : validators.values()) { + for (JsonValidator v : getValidators().values()) { errors.addAll(v.validate(jsonNode, rootNode, at)); } return errors; @@ -296,7 +291,7 @@ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { Set validationMessages = new LinkedHashSet(); // Walk through all the JSONWalker's. - for (Entry entry : validators.entrySet()) { + for (Entry entry : getValidators().entrySet()) { JsonSchemaWalker jsonWalker = entry.getValue(); String schemaPathWithKeyword = entry.getKey(); try { @@ -343,6 +338,9 @@ public JsonValidator getRequiredValidator() { } public Map getValidators() { + if (validators == null) { + validators = Collections.unmodifiableMap(this.read(getSchemaNode())); + } return validators; } diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java index 38e873e25..c53e966c1 100644 --- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java +++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java @@ -263,7 +263,7 @@ public static Builder builder(final JsonSchemaFactory blueprint) { protected JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) { final ValidationContext validationContext = createValidationContext(schemaNode); validationContext.setConfig(config); - return new JsonSchema(validationContext, schemaUri, schemaNode).initialize(); + return new JsonSchema(validationContext, schemaUri, schemaNode); } protected ValidationContext createValidationContext(final JsonNode schemaNode) { @@ -349,7 +349,6 @@ public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig co jsonSchema = new JsonSchema(validationContext, mappedUri, schemaNode); } uriSchemaCache.put(mappedUri, jsonSchema); - jsonSchema.initialize(); return jsonSchema; } finally { diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java index 2f79bcda5..727ce230f 100644 --- a/src/main/java/com/networknt/schema/NotValidator.java +++ b/src/main/java/com/networknt/schema/NotValidator.java @@ -31,8 +31,7 @@ public class NotValidator extends BaseJsonValidator implements JsonValidator { public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext); - schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema) - .initialize(); + schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema); parseErrorCode(getValidatorType().getErrorCodeKey()); } diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index e0fb51a8e..233bcbebf 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -107,9 +107,9 @@ public boolean allConstantsMatch(JsonNode node) { return true; } - private JsonSchema getSchema() { - return schema; - } + private JsonSchema getSchema() { + return schema; + } } @@ -118,8 +118,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS int size = schemaNode.size(); for (int i = 0; i < size; i++) { JsonNode childNode = schemaNode.get(i); - JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), childNode, parentSchema) - .initialize(); + JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), childNode, parentSchema); schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema)); } @@ -136,11 +135,10 @@ public Set validate(JsonNode node, JsonNode rootNode, String } // this is a complex validator, we set the flag to true state.setComplexValidator(true); - int numberOfValidSchema = 0; Set errors = new LinkedHashSet(); - + Set childErrors = new LinkedHashSet(); // validate that only a single element has been received in the oneOf node // validation should not continue, as it contradicts the oneOf requirement of only one // if(node.isObject() && node.size()>1) { @@ -152,15 +150,17 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set schemaErrors = null; // Reset state in case the previous validator did not match state.setMatchedNode(true); - if (!validator.allConstantsMatch(node)) { + + //This prevents from collecting all the error messages in proper format. + /* if (!validator.allConstantsMatch(node)) { // take a shortcut: if there is any constant that does not match, // we can bail out of the validation continue; - } + }*/ // get the current validator JsonSchema schema = validator.schema; - if (!state.isWalkEnabled()) { + if (!state.isWalkEnabled()) { schemaErrors = schema.validate(node, rootNode, at); } else { schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabledWhileWalking()); @@ -174,26 +174,41 @@ public Set validate(JsonNode node, JsonNode rootNode, String numberOfValidSchema++; } + childErrors.addAll(schemaErrors); } // ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1. - if (numberOfValidSchema != 1) { - errors = Collections.singleton(buildValidationMessage(at, "")); + if(numberOfValidSchema > 1){ + final ValidationMessage message = getMultiSchemasValidErrorMsg(at); + if( failFast ) { + throw new JsonSchemaException(message); + } + errors.add(message); + } + // ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1. + else if (numberOfValidSchema < 1) { + if (!childErrors.isEmpty()) { + errors.addAll(childErrors); + } + if( failFast ){ + throw new JsonSchemaException(errors.toString()); + } } + // reset the ValidatorState object in the ThreadLocal validatorState.remove(); return Collections.unmodifiableSet(errors); } - + public List getChildSchemas() { - List childJsonSchemas = new ArrayList(); - for (ShortcutValidator shortcutValidator: schemas ) { - childJsonSchemas.add(shortcutValidator.getSchema()); - } - return childJsonSchemas; + List childJsonSchemas = new ArrayList(); + for (ShortcutValidator shortcutValidator: schemas ) { + childJsonSchemas.add(shortcutValidator.getSchema()); + } + return childJsonSchemas; } @Override @@ -209,4 +224,18 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, return validationMessages; } + private ValidationMessage getMultiSchemasValidErrorMsg(String at){ + String msg=""; + for(ShortcutValidator schema: schemas){ + String schemaValue = schema.getSchema().getSchemaNode().toString(); + msg = msg.concat(schemaValue); + } + + ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(),ValidatorTypeCode.ONE_OF , + at, String.format("but more than one schemas {%s} are valid ",msg)); + + return message; + } + + } diff --git a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java index 6095d3d7a..d36a4fd0b 100644 --- a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java @@ -38,8 +38,7 @@ public PatternPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSc Iterator names = schemaNode.fieldNames(); while (names.hasNext()) { String name = names.next(); - schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, parentSchema.getCurrentUri(), schemaNode.get(name), parentSchema) - .initialize()); + schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, parentSchema.getCurrentUri(), schemaNode.get(name), parentSchema)); } } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index f7e19747f..cc7a74590 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -37,8 +37,7 @@ public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema pa schemas = new HashMap(); for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { String pname = it.next(); - schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema) - .initialize()); + schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema)); } propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner( config.getPropertyWalkListeners()); diff --git a/src/main/java/com/networknt/schema/PropertyNamesValidator.java b/src/main/java/com/networknt/schema/PropertyNamesValidator.java index 76c676127..23a92d5ba 100644 --- a/src/main/java/com/networknt/schema/PropertyNamesValidator.java +++ b/src/main/java/com/networknt/schema/PropertyNamesValidator.java @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.regex.Pattern; public class PropertyNamesValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(PropertyNamesValidator.class); @@ -33,8 +34,7 @@ public PropertyNamesValidator(String schemaPath, JsonNode schemaNode, JsonSchema schemas = new HashMap(); for (Iterator it = schemaNode.fieldNames(); it.hasNext(); ) { String pname = it.next(); - schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema) - .initialize()); + schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema)); } } } @@ -52,11 +52,17 @@ public Set validate(JsonNode node, JsonNode rootNode, String } for (Iterator it = node.fieldNames(); it.hasNext(); ) { String pname = it.next(); - if("maxLength".equals(entry.getKey()) && pname.length() > entry.getValue().getSchemaNode().intValue()) { - errors.add(buildValidationMessage(at + "." + pname, "maxLength")); + int maxLength = entry.getValue().getSchemaNode().intValue(); + if("maxLength".equals(entry.getKey()) && pname.length() > maxLength) { + errors.add(buildValidationMessage(at + "." + pname, "maxLength " + maxLength)); } - if("minLength".equals(entry.getKey()) && pname.length() < entry.getValue().getSchemaNode().intValue()) { - errors.add(buildValidationMessage(at + "." + pname, "minLength")); + int minLength = entry.getValue().getSchemaNode().intValue(); + if("minLength".equals(entry.getKey()) && pname.length() < minLength) { + errors.add(buildValidationMessage(at + "." + pname, "minLength " + minLength)); + } + String pattern = entry.getValue().getSchemaNode().textValue(); + if("pattern".equals(entry.getKey()) && !Pattern.matches(pattern,pname)) { + errors.add(buildValidationMessage(at + "." + pname, "pattern " + pattern)); } } } diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java index 1a6813412..51db5d31f 100644 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ b/src/main/java/com/networknt/schema/RefValidator.java @@ -97,8 +97,7 @@ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext val if (ref == null) { ref = new JsonSchemaRef(validationContext, refValue); validationContext.setReferenceParsingInProgress(refValueOriginal, ref); - JsonSchema ret = new JsonSchema(validationContext, refValue, parentSchema.getCurrentUri(), node, parentSchema) - .initialize(); + JsonSchema ret = new JsonSchema(validationContext, refValue, parentSchema.getCurrentUri(), node, parentSchema); ref.set(ret); } return ref; diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java index f6f33b08f..74934bcd1 100644 --- a/src/main/java/com/networknt/schema/UnionTypeValidator.java +++ b/src/main/java/com/networknt/schema/UnionTypeValidator.java @@ -50,8 +50,7 @@ public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema par sep = ", "; if (n.isObject()) - schemas.add(new JsonSchema(validationContext, ValidatorTypeCode.TYPE.getValue(), parentSchema.getCurrentUri(), n, parentSchema) - .initialize()); + schemas.add(new JsonSchema(validationContext, ValidatorTypeCode.TYPE.getValue(), parentSchema.getCurrentUri(), n, parentSchema)); else schemas.add(new TypeValidator(schemaPath + "/" + i, n, parentSchema, validationContext)); diff --git a/src/test/java/com/networknt/schema/Issue366FailFast.java b/src/test/java/com/networknt/schema/Issue366FailFast.java new file mode 100644 index 000000000..2e430f9ef --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue366FailFast.java @@ -0,0 +1,101 @@ +package com.networknt.schema; + +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; + +public class Issue366FailFast { + + @Before + public void setup() throws IOException { + setupSchema(); + } + + JsonSchema jsonSchema; + ObjectMapper objectMapper = new ObjectMapper(); + private void setupSchema() throws IOException { + + SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); + schemaValidatorsConfig.setFailFast(true); + JsonSchemaFactory schemaFactory = JsonSchemaFactory + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) + .objectMapper(objectMapper) + .build(); + + schemaValidatorsConfig.setTypeLoose(false); + + URI uri = getSchema(); + + InputStream in = getClass().getResourceAsStream("/draft7/issue366_schema.json"); + JsonNode testCases = objectMapper.readValue(in, JsonNode.class); + this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + public void firstOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(0); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + public void secondOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(1); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test(expected = JsonSchemaException.class) + public void bothValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(2); + JsonNode dataNode = testNode.get("data"); + jsonSchema.validate(dataNode); + } + + @Test(expected = JsonSchemaException.class) + public void neitherValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(3); + JsonNode dataNode = testNode.get("data"); + jsonSchema.validate(dataNode); + } + + private URI getSchema() { + return URI.create("classpath:" + "/draft7/issue366_schema.json"); + } +} diff --git a/src/test/java/com/networknt/schema/Issue366FailSlow.java b/src/test/java/com/networknt/schema/Issue366FailSlow.java new file mode 100644 index 000000000..8d9328542 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue366FailSlow.java @@ -0,0 +1,105 @@ +package com.networknt.schema; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; + +public class Issue366FailSlow { + + @Before + public void setup() throws IOException { + setupSchema(); + } + + JsonSchema jsonSchema; + ObjectMapper objectMapper = new ObjectMapper(); + private void setupSchema() throws IOException { + + SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); + JsonSchemaFactory schemaFactory = JsonSchemaFactory + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) + .objectMapper(objectMapper) + .build(); + + schemaValidatorsConfig.setTypeLoose(false); + + URI uri = getSchema(); + + InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json"); + JsonNode testCases = objectMapper.readValue(in, JsonNode.class); + this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(content); + return node; + } + + @Test + public void firstOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(0); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + public void secondOneValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(1); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(errors.isEmpty()); + } + + @Test + public void bothValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(2); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(!errors.isEmpty()); + assertEquals(errors.size(),1); + } + + @Test + public void neitherValid() throws Exception { + String dataPath = "/data/issue366.json"; + + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + List testNodes = node.findValues("tests"); + JsonNode testNode = testNodes.get(0).get(3); + JsonNode dataNode = testNode.get("data"); + Set errors = jsonSchema.validate(dataNode); + assertTrue(!errors.isEmpty()); + assertEquals(errors.size(),2); + } + + private URI getSchema() { + return URI.create("classpath:" + "/draft7/issue366_schema.json"); + } +} diff --git a/src/test/java/com/networknt/schema/Issue375Test.java b/src/test/java/com/networknt/schema/Issue375Test.java new file mode 100644 index 000000000..ebdc5f794 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue375Test.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Network New Technologies Inc. + * + * 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; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.SpecVersion.VersionFlag; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class Issue375Test { + protected JsonSchema getJsonSchemaFromStreamContent(InputStream schemaContent) { + JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V201909); + return factory.getSchema(schemaContent); + } + + protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(content); + } + + @Test + public void shouldFailAndShowValidationValuesWithError() throws Exception { + String schemaPath = "/draft2019-09/issue375.json"; + String dataPath = "/data/issue375.json"; + InputStream schemaInputStream = getClass().getResourceAsStream(schemaPath); + JsonSchema schema = getJsonSchemaFromStreamContent(schemaInputStream); + InputStream dataInputStream = getClass().getResourceAsStream(dataPath); + JsonNode node = getJsonNodeFromStreamContent(dataInputStream); + Set errors = schema.validate(node); + List errorMessages = new ArrayList(); + for (ValidationMessage error: errors) { + errorMessages.add(error.getMessage()); + } + + List expectedMessages = Arrays.asList( + "Property name $.fields.longName123 is not valid for validation: maxLength 5", + "Property name $.fields.longName123 is not valid for validation: pattern ^[a-zA-Z]+$", + "Property name $.fields.a is not valid for validation: minLength 3"); + MatcherAssert.assertThat(errorMessages, Matchers.containsInAnyOrder(expectedMessages.toArray())); + } +} diff --git a/src/test/resources/data/issue366.json b/src/test/resources/data/issue366.json new file mode 100644 index 000000000..ec6c9123d --- /dev/null +++ b/src/test/resources/data/issue366.json @@ -0,0 +1,24 @@ +{ + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + } \ No newline at end of file diff --git a/src/test/resources/data/issue375.json b/src/test/resources/data/issue375.json new file mode 100644 index 000000000..b940e6207 --- /dev/null +++ b/src/test/resources/data/issue375.json @@ -0,0 +1,10 @@ +{ + "fields": { + "longName123": { + "action": "doStuff" + }, + "a": { + "action": "doMagic" + } + } +} \ No newline at end of file diff --git a/src/test/resources/draft2019-09/issue375.json b/src/test/resources/draft2019-09/issue375.json new file mode 100644 index 000000000..fe6206fd2 --- /dev/null +++ b/src/test/resources/draft2019-09/issue375.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "title": "test", + "description": "asdasdint", + "type": "object", + "properties": { + "fields": { + "type": "object", + "propertyNames": { + "pattern": "^[a-zA-Z]+$", + "maxLength": 5, + "minLength": 3 + }, + "additionalProperties": { + "type": "object", + "properties": { + "action": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "action" + ] + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/draft2019-09/propertyNames.json b/src/test/resources/draft2019-09/propertyNames.json index 1a78a4084..54cd1f2ab 100644 --- a/src/test/resources/draft2019-09/propertyNames.json +++ b/src/test/resources/draft2019-09/propertyNames.json @@ -50,6 +50,35 @@ } ] }, + { + "description": "propertyNames validation with pattern", + "schema": { + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names are valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, { "description": "propertyNames with boolean schema true", "schema": { diff --git a/src/test/resources/draft4/oneOf.json b/src/test/resources/draft4/oneOf.json index 0c776c334..76b20f9e2 100644 --- a/src/test/resources/draft4/oneOf.json +++ b/src/test/resources/draft4/oneOf.json @@ -1,70 +1,4 @@ [ - { - "description": "oneOf", - "schema": { - "oneOf": [ - { - "type": "integer" - }, - { - "minimum": 2 - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": 1, - "valid": true - }, - { - "description": "second oneOf valid", - "data": 2.5, - "valid": true - }, - { - "description": "both oneOf valid", - "data": 3, - "valid": false - }, - { - "description": "neither oneOf valid", - "data": 1.5, - "valid": false - } - ] - }, - { - "description": "oneOf with base schema", - "schema": { - "type": "string", - "oneOf": [ - { - "minLength": 2 - }, - { - "maxLength": 4 - } - ] - }, - "tests": [ - { - "description": "mismatch base schema", - "data": 3, - "valid": false - }, - { - "description": "one oneOf valid", - "data": "foobar", - "valid": true - }, - { - "description": "both oneOf valid", - "data": "foo", - "valid": false - } - ] - }, { "description": "oneOf with objects and type pattern", "schema": { @@ -100,139 +34,6 @@ "tests": [ { "description": "first oneOf valid", - "data": { - "type": "TYPE_1" - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "type": "TYPE_2" - }, - "valid": true - }, - { - "description": "neither oneOf valid", - "data": { - "type": "INVALID_TYPE" - }, - "valid": false - } - ] - }, - { - "description": "oneOf with objects and type pattern", - "schema": { - "oneOf": [ - { - "type": "object", - "required": [ - "id" - ], - "properties": { - "color": { - "type": "string" - }, - "id": { - "type": "string" - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "enum": [ - "TYPE_2" - ] - } - } - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": { - "color": "black", - "id": "abc" - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "type": "TYPE_2" - }, - "valid": true - }, - { - "description": "neither oneOf valid", - "data": { - "type": "INVALID_TYPE" - }, - "valid": false - }, - { - "description": "first oneOf missing property but still valid", - "data": { - "id": "abc" - }, - "valid": true - } - ] - }, - { - "description": "oneOf with objects and regex pattern", - "schema": { - "oneOf": [ - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "pattern": "^TYPE_1$" - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "pattern": "^TYPE_2$" - } - } - } - ] - }, - "tests": [ - { - "description": "first oneOf valid", - "data": { - "type": "TYPE_1" - }, - "valid": true - }, - { - "description": "second oneOf valid", - "data": { - "type": "TYPE_2" - }, - "valid": true - }, - { - "description": "neither oneOf valid", "data": { "type": "INVALID_TYPE" }, @@ -240,4 +41,4 @@ } ] } -] + ] \ No newline at end of file diff --git a/src/test/resources/draft6/propertyNames.json b/src/test/resources/draft6/propertyNames.json index 1a78a4084..54cd1f2ab 100644 --- a/src/test/resources/draft6/propertyNames.json +++ b/src/test/resources/draft6/propertyNames.json @@ -50,6 +50,35 @@ } ] }, + { + "description": "propertyNames validation with pattern", + "schema": { + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names are valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, { "description": "propertyNames with boolean schema true", "schema": { diff --git a/src/test/resources/draft7/propertyNames.json b/src/test/resources/draft7/propertyNames.json index 1a78a4084..54cd1f2ab 100644 --- a/src/test/resources/draft7/propertyNames.json +++ b/src/test/resources/draft7/propertyNames.json @@ -50,6 +50,35 @@ } ] }, + { + "description": "propertyNames validation with pattern", + "schema": { + "propertyNames": { "pattern": "^a+$" } + }, + "tests": [ + { + "description": "matching property names are valid", + "data": { + "a": {}, + "aa": {}, + "aaa": {} + }, + "valid": true + }, + { + "description": "non-matching property name is invalid", + "data": { + "aaA": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + } + ] + }, { "description": "propertyNames with boolean schema true", "schema": { diff --git a/src/test/resources/schema/issue366_schema.json b/src/test/resources/schema/issue366_schema.json new file mode 100644 index 000000000..b3975969d --- /dev/null +++ b/src/test/resources/schema/issue366_schema.json @@ -0,0 +1,11 @@ +{ + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] +} + From 4486cc47ac50e18e12b102577d32e4032bdfe348 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Fri, 12 Mar 2021 16:18:11 +0530 Subject: [PATCH 07/14] Fixing issues with ValidatorState --- .../schema/AdditionalPropertiesValidator.java | 2 +- .../networknt/schema/BaseJsonValidator.java | 5 - .../networknt/schema/CollectorContext.java | 2 +- .../java/com/networknt/schema/JsonSchema.java | 113 ++++++++++-------- .../com/networknt/schema/OneOfValidator.java | 8 +- .../networknt/schema/PropertiesValidator.java | 7 +- .../com/networknt/schema/ValidatorState.java | 3 + .../walk/AbstractWalkListenerRunner.java | 15 ++- .../com/networknt/schema/walk/WalkEvent.java | 4 + .../com/networknt/schema/walk/WalkFlow.java | 6 +- .../schema/CollectorContextTest.java | 58 ++++----- .../com/networknt/schema/JsonWalkTest.java | 23 ++-- 12 files changed, 128 insertions(+), 118 deletions(-) diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java index f4f10cf02..823ea13ab 100644 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java @@ -93,7 +93,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String errors.add(buildValidationMessage(at, pname)); } else { if (additionalPropertiesSchema != null) { - ValidatorState state = validatorState.get(); + ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); if (state != null && state.isWalkEnabled()) { errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabledWhileWalking())); } else { diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java index 53d1c8fa3..629925997 100644 --- a/src/main/java/com/networknt/schema/BaseJsonValidator.java +++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java @@ -37,11 +37,6 @@ public abstract class BaseJsonValidator implements JsonValidator { protected SchemaValidatorsConfig config; protected final boolean failFast; - /** - * ThreadLocal to allow to pass state in recursive validator calls - */ - protected final static ThreadLocal validatorState = new ThreadLocal(); - public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidatorTypeCode validatorType, ValidationContext validationContext) { this(schemaPath, schemaNode, parentSchema, validatorType, false, diff --git a/src/main/java/com/networknt/schema/CollectorContext.java b/src/main/java/com/networknt/schema/CollectorContext.java index ea895f052..b2b242f09 100644 --- a/src/main/java/com/networknt/schema/CollectorContext.java +++ b/src/main/java/com/networknt/schema/CollectorContext.java @@ -118,7 +118,7 @@ public void combineWithCollector(String name, Object data) { /** * Reset the context */ - void reset() { + public void reset() { this.collectorMap = new HashMap(); this.collectorLoadMap = new HashMap(); } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 098c90fc0..1837055b6 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -220,7 +220,12 @@ private Map read(JsonNode schemaNode) { public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { Set errors = new LinkedHashSet(); + // create and get the collector context. + createAndGetCollectorContext(); + // Set the walkEnabled and isValidationEnabledWhileWalking flag in internal validator state. + setValidatorState(false, true); for (JsonValidator v : getValidators().values()) { + // Validate. errors.addAll(v.validate(jsonNode, rootNode, at)); } return errors; @@ -241,14 +246,9 @@ public ValidationResult validateAndCollect(JsonNode node) { */ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) { try { - CollectorContext collectorContext; - if(this.config !=null && this.config.getCollectorContext() != null){ - collectorContext = this.config.getCollectorContext(); - } else { - collectorContext = new CollectorContext(); - } - // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); + // create and get the collector context. + CollectorContext collectorContext = createAndGetCollectorContext(); + // Valdiate. Set errors = validate(jsonNode, rootNode, at); // Load all the data from collectors into the context. collectorContext.loadCollectors(); @@ -271,58 +271,71 @@ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNo * @return result of ValidationResult */ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { - // Create the collector context object. - CollectorContext collectorContext = new CollectorContext(); - // Set the collector context in thread info, this is unique for every thread. - ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); + // create and get the collector context. + CollectorContext collectorContext = createAndGetCollectorContext(); // Set the walkEnabled flag in internal validator state. setValidatorState(true, shouldValidateSchema); // Walk through the schema. - Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); - // Load all the data from collectors into the context. - collectorContext.loadCollectors(); - // Collect errors and collector context into validation result. - ValidationResult validationResult = new ValidationResult(errors, collectorContext); - return validationResult; - } + Set errors = walk(node, node, AT_ROOT, shouldValidateSchema); + // Load all the data from collectors into the context. + collectorContext.loadCollectors(); + // Collect errors and collector context into validation result. + ValidationResult validationResult = new ValidationResult(errors, collectorContext); + return validationResult; + } @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - Set validationMessages = new LinkedHashSet(); - // Walk through all the JSONWalker's. - for (Entry entry : getValidators().entrySet()) { - JsonSchemaWalker jsonWalker = entry.getValue(); - String schemaPathWithKeyword = entry.getKey(); - try { - // Call all the pre-walk listeners. If all the pre-walk listeners return true - // then continue to walk method. - if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema, validationContext.getJsonSchemaFactory())) { - validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); - } - } finally { - // Call all the post-walk listeners. - keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, - schemaNode, parentSchema, validationContext.getJsonSchemaFactory(), validationMessages); - } - } - return validationMessages; + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + Set validationMessages = new LinkedHashSet(); + // Walk through all the JSONWalker's. + for (Entry entry : getValidators().entrySet()) { + JsonSchemaWalker jsonWalker = entry.getValue(); + String schemaPathWithKeyword = entry.getKey(); + try { + // Call all the pre-walk listeners. If atleast one of the pre walk listeners + // returns SKIP_WALK, then skip the walk. + if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, + schemaNode, parentSchema, validationContext.getJsonSchemaFactory())) { + validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); + } + } finally { + // Call all the post-walk listeners. + keywordWalkListenerRunner.runPostWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, + schemaNode, parentSchema, validationContext.getJsonSchemaFactory(), validationMessages); + } + } + return validationMessages; } /************************ END OF WALK METHODS **********************************/ - private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSchema) { - // Get the Validator state object storing validation data - ValidatorState state = validatorState.get(); - if (state == null) { - // if one has not been created, instantiate one - state = new ValidatorState(); - state.setWalkEnabled(isWalkEnabled); - state.setValidationEnabledWhileWalking(shouldValidateSchema); - validatorState.set(state); - } - } + private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSchema) { + // Get the Validator state object storing validation data + Object stateObj = CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); + // if one has not been created, instantiate one + if (stateObj == null) { + ValidatorState state = new ValidatorState(); + state.setWalkEnabled(isWalkEnabled); + state.setValidationEnabledWhileWalking(shouldValidateSchema); + CollectorContext.getInstance().add(ValidatorState.VALIDATOR_STATE_KEY, state); + } + } + + + public CollectorContext createAndGetCollectorContext() { + CollectorContext collectorContext = (CollectorContext) ThreadInfo.get(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); + if (collectorContext == null) { + if (this.config != null && this.config.getCollectorContext() != null) { + collectorContext = this.config.getCollectorContext(); + } else { + collectorContext = new CollectorContext(); + } + // Set the collector context in thread info, this is unique for every thread. + ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext); + } + return collectorContext; + } @Override public String toString() { diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index 977a4a279..f47ab66b6 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -128,11 +128,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - ValidatorState state = validatorState.get(); - if (state == null) { - state = new ValidatorState(); - validatorState.set(state); - } + ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); // this is a complex validator, we set the flag to true state.setComplexValidator(true); @@ -198,7 +194,7 @@ else if (numberOfValidSchema < 1) { // reset the ValidatorState object in the ThreadLocal - validatorState.remove(); + CollectorContext.getInstance().add(ValidatorState.VALIDATOR_STATE_KEY, null); return Collections.unmodifiableSet(errors); } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index cc7a74590..7b54c5094 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -49,12 +49,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); // get the Validator state object storing validation data - ValidatorState state = validatorState.get(); - if (state == null) { - // if one has not been created, instantiate one - state = new ValidatorState(); - validatorState.set(state); - } + ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); for (Map.Entry entry : schemas.entrySet()) { JsonSchema propertySchema = entry.getValue(); diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java index e3a80d859..5230852cd 100644 --- a/src/main/java/com/networknt/schema/ValidatorState.java +++ b/src/main/java/com/networknt/schema/ValidatorState.java @@ -16,6 +16,9 @@ package com.networknt.schema; public class ValidatorState { + + public static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState"; + /** * Flag set when a node has matched Works in conjunction with the next flag: * isComplexValidator, to be used for complex validators such as oneOf, for ex diff --git a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java index 23631fb86..e21db44b0 100644 --- a/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java +++ b/src/main/java/com/networknt/schema/walk/AbstractWalkListenerRunner.java @@ -23,20 +23,23 @@ protected WalkEvent constructWalkEvent(String keyWordName, JsonNode node, JsonNo } protected boolean runPreWalkListeners(List walkListeners, WalkEvent walkEvent) { - boolean continueRunningListenersAndWalk = true; + boolean continueToWalkMethod = true; if (walkListeners != null) { for (JsonSchemaWalkListener walkListener : walkListeners) { - if (WalkFlow.SKIP.equals(walkListener.onWalkStart(walkEvent))) { - continueRunningListenersAndWalk = false; - break; + WalkFlow walkFlow = walkListener.onWalkStart(walkEvent); + if (WalkFlow.SKIP.equals(walkFlow) || WalkFlow.ABORT.equals(walkFlow)) { + continueToWalkMethod = false; + if (WalkFlow.ABORT.equals(walkFlow)) { + break; + } } } } - return continueRunningListenersAndWalk; + return continueToWalkMethod; } protected void runPostWalkListeners(List walkListeners, WalkEvent walkEvent, - Set validationMessages) { + Set validationMessages) { if (walkListeners != null) { for (JsonSchemaWalkListener walkListener : walkListeners) { walkListener.onWalkEnd(walkEvent, validationMessages); diff --git a/src/main/java/com/networknt/schema/walk/WalkEvent.java b/src/main/java/com/networknt/schema/walk/WalkEvent.java index 97a04373d..cd950d6a7 100644 --- a/src/main/java/com/networknt/schema/walk/WalkEvent.java +++ b/src/main/java/com/networknt/schema/walk/WalkEvent.java @@ -54,6 +54,10 @@ public JsonSchema getRefSchema(URI schemaUri) { return currentJsonSchemaFactory.getSchema(schemaUri); } + public JsonSchemaFactory getCurrentJsonSchemaFactory() { + return currentJsonSchemaFactory; + } + static class WalkEventBuilder { private WalkEvent keywordWalkEvent = null; diff --git a/src/main/java/com/networknt/schema/walk/WalkFlow.java b/src/main/java/com/networknt/schema/walk/WalkFlow.java index 04d3cbbf2..af2da7b5c 100644 --- a/src/main/java/com/networknt/schema/walk/WalkFlow.java +++ b/src/main/java/com/networknt/schema/walk/WalkFlow.java @@ -2,9 +2,11 @@ public enum WalkFlow { - SKIP("SkipWalk", "Skip the walk methods"), + SKIP("SkipWalk", "Skip only the walk method, but continue invoking the other listeners"), - CONTINUE("ContinueToWalk", "continue to invoke the walk method"); + ABORT("Abort", "Aborts all the walk listeners and walk method itself"), + + CONTINUE("ContinueToWalk", "continue to invoke the walk method and other listeners"); private String name; diff --git a/src/test/java/com/networknt/schema/CollectorContextTest.java b/src/test/java/com/networknt/schema/CollectorContextTest.java index dcbdf5661..2ba81aebe 100644 --- a/src/test/java/com/networknt/schema/CollectorContextTest.java +++ b/src/test/java/com/networknt/schema/CollectorContextTest.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -28,20 +30,25 @@ public class CollectorContextTest { - private static final String SAMPLE_COLLECTOR = "sampleCollectorType"; + private static final String SAMPLE_COLLECTOR = "sampleCollector"; - private static final String SAMPLE_COLLECTOR_OTHER = "sampleCollectorOtherType"; + private static final String SAMPLE_COLLECTOR_OTHER = "sampleCollectorOther"; private JsonSchema jsonSchema; private JsonSchema jsonSchemaForCombine; - @Before public void setup() throws Exception { setupSchema(); } + @After + public void cleanup() { + if (CollectorContext.getInstance() != null) { + CollectorContext.getInstance().reset(); + } + } @SuppressWarnings("unchecked") @Test @@ -57,7 +64,7 @@ public void testCollectorContextWithKeyword() throws Exception { @SuppressWarnings("unchecked") @Test - public void testCollectorContextWithMultiplThreads() throws Exception { + public void testCollectorContextWithMultipleThreads() throws Exception { ValidationThread validationRunnable1 = new ValidationThread("{\"test-property1\":\"sample1\" }", "thread1"); ValidationThread validationRunnable2 = new ValidationThread("{\"test-property1\":\"sample2\" }", "thread2"); @@ -95,28 +102,15 @@ public void testCollectorContextWithMultiplThreads() throws Exception { Assert.assertEquals(contextValue3.get(0), "actual_value_added_to_context3"); } - @SuppressWarnings("unchecked") - @Test - public void testCollectorWithFormat() throws JsonMappingException, JsonProcessingException, IOException { - ObjectMapper objectMapper = new ObjectMapper(); - ValidationResult validationResult = jsonSchemaForCombine.validateAndCollect(objectMapper - .readTree("{\"property1\":\"sample1\",\"property2\":\"sample2\",\"property3\":\"sample3\" }")); - List values = (List) validationResult.getCollectorContext().get(SAMPLE_COLLECTOR); - List values1 = (List) validationResult.getCollectorContext().get(SAMPLE_COLLECTOR_OTHER); - Assert.assertEquals(values.size(), 1); - Assert.assertEquals(values1.size(), 3); - } - @SuppressWarnings("unchecked") @Test public void testCollectorGetAll() throws JsonMappingException, JsonProcessingException, IOException { ObjectMapper objectMapper = new ObjectMapper(); ValidationResult validationResult = jsonSchemaForCombine.validateAndCollect(objectMapper .readTree("{\"property1\":\"sample1\",\"property2\":\"sample2\",\"property3\":\"sample3\" }")); - Map map = validationResult.getCollectorContext().getAll(); - Iterator collectionIterator = map.values().iterator(); - Assert.assertEquals(((List) collectionIterator.next()).size(), 1); - Assert.assertEquals(((List) collectionIterator.next()).size(), 3); + CollectorContext collectorContext = validationResult.getCollectorContext(); + Assert.assertEquals(((List) collectorContext.get(SAMPLE_COLLECTOR)).size(), 1); + Assert.assertEquals(((List) collectorContext.get(SAMPLE_COLLECTOR_OTHER)).size(), 3); } private JsonMetaSchema getJsonMetaSchema(String uri) throws Exception { @@ -216,7 +210,6 @@ private String getSchemaStringMultipleProperties() { + "}"; } - private class ValidationThread implements Runnable { private String data; @@ -266,7 +259,7 @@ public String getValue() { @Override public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { + ValidationContext validationContext) throws JsonSchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { return new CustomValidator(); } @@ -274,7 +267,6 @@ public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSc } } - /** * We will be collecting information/data by adding the data in the form of * collectors into collector context object while we are validating this node. @@ -299,11 +291,11 @@ public Set validate(JsonNode rootNode) { return validate(rootNode, rootNode, BaseJsonValidator.AT_ROOT); } - @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - // Ignore this method for testing. - return null; - } + @Override + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + // Ignore this method for testing. + return null; + } } @@ -341,7 +333,7 @@ public String getValue() { @Override public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, - ValidationContext validationContext) throws JsonSchemaException, Exception { + ValidationContext validationContext) throws JsonSchemaException, Exception { if (schemaNode != null && schemaNode.isArray()) { return new CustomValidator1(); } @@ -378,10 +370,10 @@ public Set validate(JsonNode rootNode) { } @Override - public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { - // Ignore this method for testing. - return null; - } + public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + // Ignore this method for testing. + return null; + } } private ValidationResult validate(String jsonData) throws JsonMappingException, JsonProcessingException, Exception { diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 26c902d8f..ae9422341 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -6,6 +6,8 @@ import com.networknt.schema.walk.JsonSchemaWalkListener; import com.networknt.schema.walk.WalkEvent; import com.networknt.schema.walk.WalkFlow; + +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -21,7 +23,7 @@ public class JsonWalkTest { private JsonSchema jsonSchema; - private static final String SAMPLE_COLLECTOR = "sampleCollectorType"; + private static final String SAMPLE_WALK_COLLECTOR_TYPE = "sampleWalkCollectorType"; private static final String CUSTOM_KEYWORD = "custom-keyword"; @@ -30,6 +32,11 @@ public void setup() { setupSchema(); } + @After + public void cleanup() { + CollectorContext.getInstance().reset(); + } + private void setupSchema() { final JsonMetaSchema metaSchema = getJsonMetaSchema(); SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); @@ -54,7 +61,7 @@ public void testWalk() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); ValidationResult result = jsonSchema.walk( objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); - JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_COLLECTOR); + JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); assertEquals(collectedNode, (objectMapper.readTree("{" + " \"PROPERTY1\": \"sample1\"," + " \"PROPERTY2\": \"sample2\"," @@ -123,11 +130,11 @@ public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { String keyWordName = keywordWalkEvent.getKeyWordName(); JsonNode schemaNode = keywordWalkEvent.getSchemaNode(); CollectorContext collectorContext = CollectorContext.getInstance(); - if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, mapper.createObjectNode()); + if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { + collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } if (keyWordName.equals(CUSTOM_KEYWORD) && schemaNode.get(CUSTOM_KEYWORD).isArray()) { - ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_COLLECTOR); + ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); objectNode.put(keywordWalkEvent.getSchemaNode().get("title").textValue().toUpperCase(), keywordWalkEvent.getNode().textValue()); } @@ -146,10 +153,10 @@ private static class RefKeywordListener implements JsonSchemaWalkListener { public WalkFlow onWalkStart(WalkEvent keywordWalkEvent) { ObjectMapper mapper = new ObjectMapper(); CollectorContext collectorContext = CollectorContext.getInstance(); - if (collectorContext.get(SAMPLE_COLLECTOR) == null) { - collectorContext.add(SAMPLE_COLLECTOR, mapper.createObjectNode()); + if (collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE) == null) { + collectorContext.add(SAMPLE_WALK_COLLECTOR_TYPE, mapper.createObjectNode()); } - ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_COLLECTOR); + ObjectNode objectNode = (ObjectNode) collectorContext.get(SAMPLE_WALK_COLLECTOR_TYPE); objectNode.set(keywordWalkEvent.getSchemaNode().get("title").textValue().toLowerCase(), keywordWalkEvent.getNode()); return WalkFlow.SKIP; From cfbdf1c619520d58bbb0c28670776e6d08c33998 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Mon, 15 Mar 2021 16:54:34 +0530 Subject: [PATCH 08/14] changing the method name to getCollectorContext --- .../java/com/networknt/schema/JsonSchema.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 1837055b6..b85d6f5d2 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -220,8 +220,8 @@ private Map read(JsonNode schemaNode) { public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { Set errors = new LinkedHashSet(); - // create and get the collector context. - createAndGetCollectorContext(); + // Get the collector context. + getCollectorContext(); // Set the walkEnabled and isValidationEnabledWhileWalking flag in internal validator state. setValidatorState(false, true); for (JsonValidator v : getValidators().values()) { @@ -235,19 +235,20 @@ public ValidationResult validateAndCollect(JsonNode node) { return validateAndCollect(node, node, AT_ROOT); } - /** - * This method both validates and collects the data in a CollectionContext. + * This method both validates and collects the data in a CollectorContext. + * Unlike others this methods cleans and removes everything from collector + * context before returning. * * @param jsonNode JsonNode * @param rootNode JsonNode - * @param at String path + * @param at String path * @return ValidationResult */ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) { try { - // create and get the collector context. - CollectorContext collectorContext = createAndGetCollectorContext(); + // Get the collector context from the thread local. + CollectorContext collectorContext = getCollectorContext(); // Valdiate. Set errors = validate(jsonNode, rootNode, at); // Load all the data from collectors into the context. @@ -271,8 +272,8 @@ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNo * @return result of ValidationResult */ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { - // create and get the collector context. - CollectorContext collectorContext = createAndGetCollectorContext(); + // Get the collector context from the thread local. + CollectorContext collectorContext = getCollectorContext(); // Set the walkEnabled flag in internal validator state. setValidatorState(true, shouldValidateSchema); // Walk through the schema. @@ -323,7 +324,7 @@ private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSche } - public CollectorContext createAndGetCollectorContext() { + public CollectorContext getCollectorContext() { CollectorContext collectorContext = (CollectorContext) ThreadInfo.get(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); if (collectorContext == null) { if (this.config != null && this.config.getCollectorContext() != null) { From d125abd8a68123163ff333a2b2093a8573abbf54 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Mon, 15 Mar 2021 17:31:16 +0530 Subject: [PATCH 09/14] correcting the variable names and ValidatorState logic --- .../schema/AdditionalPropertiesValidator.java | 2 +- src/main/java/com/networknt/schema/JsonSchema.java | 4 ++-- .../java/com/networknt/schema/OneOfValidator.java | 11 ++++++++--- .../com/networknt/schema/PropertiesValidator.java | 2 +- .../java/com/networknt/schema/ValidatorState.java | 12 ++++++------ 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java index 823ea13ab..505d50eb4 100644 --- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java @@ -95,7 +95,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (additionalPropertiesSchema != null) { ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); if (state != null && state.isWalkEnabled()) { - errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabledWhileWalking())); + errors.addAll(additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabled())); } else { errors.addAll(additionalPropertiesSchema.validate(node.get(pname), rootNode, at + "." + pname)); } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index b85d6f5d2..f5205f809 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -295,7 +295,7 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, String schemaPathWithKeyword = entry.getKey(); try { // Call all the pre-walk listeners. If atleast one of the pre walk listeners - // returns SKIP_WALK, then skip the walk. + // returns SKIP, then skip the walk. if (keywordWalkListenerRunner.runPreWalkListeners(schemaPathWithKeyword, node, rootNode, at, schemaPath, schemaNode, parentSchema, validationContext.getJsonSchemaFactory())) { validationMessages.addAll(jsonWalker.walk(node, rootNode, at, shouldValidateSchema)); @@ -318,7 +318,7 @@ private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSche if (stateObj == null) { ValidatorState state = new ValidatorState(); state.setWalkEnabled(isWalkEnabled); - state.setValidationEnabledWhileWalking(shouldValidateSchema); + state.setValidationEnabled(shouldValidateSchema); CollectorContext.getInstance().add(ValidatorState.VALIDATOR_STATE_KEY, state); } } diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index f47ab66b6..b776db5e1 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -159,7 +159,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (!state.isWalkEnabled()) { schemaErrors = schema.validate(node, rootNode, at); } else { - schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabledWhileWalking()); + schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabled()); } // check if any validation errors have occurred @@ -192,13 +192,18 @@ else if (numberOfValidSchema < 1) { } } - // reset the ValidatorState object in the ThreadLocal - CollectorContext.getInstance().add(ValidatorState.VALIDATOR_STATE_KEY, null); + resetValidatorState(); return Collections.unmodifiableSet(errors); } + private void resetValidatorState() { + ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); + state.setComplexValidator(false); + state.setMatchedNode(true); + } + public List getChildSchemas() { List childJsonSchemas = new ArrayList(); for (ShortcutValidator shortcutValidator: schemas ) { diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 7b54c5094..67ef30ef0 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -70,7 +70,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); } else { // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. - walkSchema(entry, node, rootNode, at, state.isValidationEnabledWhileWalking(), errors); + walkSchema(entry, node, rootNode, at, state.isValidationEnabled(), errors); } // reset the complex flag to the original value before the recursive call diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java index 5230852cd..2664ea126 100644 --- a/src/main/java/com/networknt/schema/ValidatorState.java +++ b/src/main/java/com/networknt/schema/ValidatorState.java @@ -17,7 +17,7 @@ public class ValidatorState { - public static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState"; + static final String VALIDATOR_STATE_KEY = "com.networknt.schema.ValidatorState"; /** * Flag set when a node has matched Works in conjunction with the next flag: @@ -40,7 +40,7 @@ public class ValidatorState { /** * Flag to check if validation is enabled while walking. */ - private boolean isValidationEnabledWhileWalking = false; + private boolean isValidationEnabled = false; public void setMatchedNode(boolean matchedNode) { this.matchedNode = matchedNode; @@ -66,12 +66,12 @@ public void setWalkEnabled(boolean isWalkEnabled) { this.isWalkEnabled = isWalkEnabled; } - public boolean isValidationEnabledWhileWalking() { - return isValidationEnabledWhileWalking; + public boolean isValidationEnabled() { + return isValidationEnabled; } - public void setValidationEnabledWhileWalking(boolean isValidationEnabledWhileWalking) { - this.isValidationEnabledWhileWalking = isValidationEnabledWhileWalking; + public void setValidationEnabled(boolean isValidationEnabled) { + this.isValidationEnabled = isValidationEnabled; } } From abd45a8e18267e6c9f1339ecced07fbccf30f55f Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Mon, 15 Mar 2021 17:47:23 +0530 Subject: [PATCH 10/14] correcting the documentation logic --- src/main/java/com/networknt/schema/JsonSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index f5205f809..ac6497c79 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -222,7 +222,7 @@ public Set validate(JsonNode jsonNode, JsonNode rootNode, Str Set errors = new LinkedHashSet(); // Get the collector context. getCollectorContext(); - // Set the walkEnabled and isValidationEnabledWhileWalking flag in internal validator state. + // Set the walkEnabled and isValidationEnabled flag in internal validator state. setValidatorState(false, true); for (JsonValidator v : getValidators().values()) { // Validate. From 34b8cf70d2b85feb98ca6329dd7389a96b515029 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Mon, 15 Mar 2021 18:24:29 +0530 Subject: [PATCH 11/14] Fixing compilation issues in TypeFactoryTest --- .../com/networknt/schema/TypeFactoryTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/networknt/schema/TypeFactoryTest.java b/src/test/java/com/networknt/schema/TypeFactoryTest.java index a1ab4d535..69f5bcbeb 100755 --- a/src/test/java/com/networknt/schema/TypeFactoryTest.java +++ b/src/test/java/com/networknt/schema/TypeFactoryTest.java @@ -51,23 +51,23 @@ public void testValidIntegralValuesWithoutJavaSemantics() { } } - @Test - public void testWithLosslessNarrowing() { - schemaValidatorsConfig.setLosslessNarrowing(true); - assertSame(validValue, JsonType.INTEGER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig)); + // @Test + // public void testWithLosslessNarrowing() { + // schemaValidatorsConfig.setLosslessNarrowing(true); + // assertSame(validValue, JsonType.INTEGER, + // getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig)); - assertSame(validValue, JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig)); - } + // assertSame(validValue, JsonType.NUMBER, + // getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig)); + // } - @Test - public void testWithoutLosslessNarrowing() { - schemaValidatorsConfig.setLosslessNarrowing(false); - assertSame(validValue, JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig)); + // @Test + // public void testWithoutLosslessNarrowing() { + // schemaValidatorsConfig.setLosslessNarrowing(false); + // assertSame(validValue, JsonType.NUMBER, + // getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.0")), schemaValidatorsConfig)); - assertSame(validValue, JsonType.NUMBER, - getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig)); - } + // assertSame(validValue, JsonType.NUMBER, + // getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig)); + // } } From beb65696cb00d9f04f14a6fccdaf3fc0ff4cc98f Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Wed, 17 Mar 2021 21:00:42 +0530 Subject: [PATCH 12/14] Fixing the minor brace issue --- src/test/java/com/networknt/schema/TypeFactoryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/networknt/schema/TypeFactoryTest.java b/src/test/java/com/networknt/schema/TypeFactoryTest.java index 77cfdb56c..d20a2cfe1 100755 --- a/src/test/java/com/networknt/schema/TypeFactoryTest.java +++ b/src/test/java/com/networknt/schema/TypeFactoryTest.java @@ -75,4 +75,5 @@ public void testWithoutLosslessNarrowing() { getValueNodeType(DecimalNode.valueOf(new BigDecimal("1.5")), schemaValidatorsConfig)); } -} + } +} \ No newline at end of file From 5b59c56d491945f8bc711d63471ea2af64cb65cd Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Thu, 2 Sep 2021 17:49:21 +0530 Subject: [PATCH 13/14] Fixing the issue of propogating walk-listeners to child 's from parent schema --- .../com/networknt/schema/AllOfValidator.java | 2 +- .../com/networknt/schema/AnyOfValidator.java | 26 ++++++++--------- .../networknt/schema/BaseJsonValidator.java | 6 +--- .../networknt/schema/DateTimeValidator.java | 5 +++- .../com/networknt/schema/EnumValidator.java | 9 +++--- .../schema/ExclusiveMaximumValidator.java | 6 ++-- .../schema/ExclusiveMinimumValidator.java | 6 ++-- .../com/networknt/schema/FormatValidator.java | 4 ++- .../com/networknt/schema/ItemsValidator.java | 6 ++-- .../java/com/networknt/schema/JsonSchema.java | 22 +++++++++----- .../networknt/schema/JsonSchemaFactory.java | 10 +++++-- .../networknt/schema/MaxItemsValidator.java | 7 +++-- .../networknt/schema/MaxLengthValidator.java | 6 ++-- .../networknt/schema/MaximumValidator.java | 5 ++-- .../networknt/schema/MinItemsValidator.java | 6 ++-- .../networknt/schema/MinLengthValidator.java | 6 ++-- .../networknt/schema/MinimumValidator.java | 5 +++- .../com/networknt/schema/OneOfValidator.java | 18 ++++++++---- .../networknt/schema/PatternValidator.java | 15 ++++++---- .../networknt/schema/PropertiesValidator.java | 11 +++---- .../com/networknt/schema/RefValidator.java | 12 +++++++- .../com/networknt/schema/TypeFactory.java | 4 +-- .../com/networknt/schema/TypeValidator.java | 14 +++++---- .../com/networknt/schema/UUIDValidator.java | 4 ++- .../networknt/schema/UnionTypeValidator.java | 6 ++-- .../networknt/schema/ValidationContext.java | 3 ++ .../schema/format/EmailValidator.java | 5 +++- .../networknt/schema/utils/JsonNodeUtil.java | 2 +- .../com/networknt/schema/JsonWalkTest.java | 29 +++++++++++++++++++ 29 files changed, 177 insertions(+), 83 deletions(-) diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java index f1312bf62..79ee22c2d 100644 --- a/src/main/java/com/networknt/schema/AllOfValidator.java +++ b/src/main/java/com/networknt/schema/AllOfValidator.java @@ -55,7 +55,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String for (JsonSchema schema : schemas) { errors.addAll(schema.validate(node, rootNode, at)); - if (config.isOpenAPI3StyleDiscriminators()) { + if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { final Iterator arrayElements = schemaNode.elements(); while (arrayElements.hasNext()) { final ObjectNode allOfEntry = (ObjectNode) arrayElements.next(); diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java index 5b1a471bc..15edf5c55 100644 --- a/src/main/java/com/networknt/schema/AnyOfValidator.java +++ b/src/main/java/com/networknt/schema/AnyOfValidator.java @@ -37,13 +37,13 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS int size = schemaNode.size(); for (int i = 0; i < size; i++) { schemas.add(new JsonSchema(validationContext, - getValidatorType().getValue(), - parentSchema.getCurrentUri(), - schemaNode.get(i), - parentSchema)); + getValidatorType().getValue(), + parentSchema.getCurrentUri(), + schemaNode.get(i), + parentSchema)); } - if (config.isOpenAPI3StyleDiscriminators()) { + if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { this.discriminatorContext = new ValidationContext.DiscriminatorContext(); } else { this.discriminatorContext = null; @@ -53,7 +53,7 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - if (config.isOpenAPI3StyleDiscriminators()) { + if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { validationContext.enterDiscriminatorContext(this.discriminatorContext, at); } @@ -72,9 +72,9 @@ public Set validate(JsonNode node, JsonNode rootNode, String } } Set errors = schema.validate(node, rootNode, at); - if (errors.isEmpty() && !config.isOpenAPI3StyleDiscriminators()) { + if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())) { return errors; - } else if (config.isOpenAPI3StyleDiscriminators()) { + } else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { if (discriminatorContext.isDiscriminatorMatchFound()) { if (!errors.isEmpty()) { errors.add(buildValidationMessage(at, DISCRIMINATOR_REMARK)); @@ -85,13 +85,13 @@ public Set validate(JsonNode node, JsonNode rootNode, String allErrors.addAll(errors); } - if (config.isOpenAPI3StyleDiscriminators() && discriminatorContext.isActive()) { - final Set errors = new HashSet(); - errors.add(buildValidationMessage(at, "based on the provided discriminator. No alternative could be chosen based on the discriminator property")); - return Collections.unmodifiableSet(errors); + if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators() && discriminatorContext.isActive()) { + final Set errors = new HashSet(); + errors.add(buildValidationMessage(at, "based on the provided discriminator. No alternative could be chosen based on the discriminator property")); + return Collections.unmodifiableSet(errors); } } finally { - if (config.isOpenAPI3StyleDiscriminators()) { + if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { validationContext.leaveDiscriminatorContextImmediately(at); } } diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java index 9ad401b7a..b410c1b81 100644 --- a/src/main/java/com/networknt/schema/BaseJsonValidator.java +++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java @@ -36,17 +36,13 @@ public abstract class BaseJsonValidator implements JsonValidator { private boolean suppressSubSchemaRetrieval; private ValidatorTypeCode validatorType; private ErrorMessageType errorMessageType; - /** - * SchemaValidatorsConfig can only get and set in validationContext - */ - protected SchemaValidatorsConfig config; + protected final boolean failFast; public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidatorTypeCode validatorType, ValidationContext validationContext) { this(schemaPath, schemaNode, parentSchema, validatorType, false, validationContext.getConfig() != null && validationContext.getConfig().isFailFast()); - this.config = validationContext.getConfig() == null ? new SchemaValidatorsConfig() : validationContext.getConfig(); } public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, diff --git a/src/main/java/com/networknt/schema/DateTimeValidator.java b/src/main/java/com/networknt/schema/DateTimeValidator.java index ed4d01fe5..420aaacda 100644 --- a/src/main/java/com/networknt/schema/DateTimeValidator.java +++ b/src/main/java/com/networknt/schema/DateTimeValidator.java @@ -32,6 +32,8 @@ public class DateTimeValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(DateTimeValidator.class); + private final ValidationContext validationContext; + private final String formatName; private final String DATE = "date"; private final String DATETIME = "date-time"; @@ -44,6 +46,7 @@ public class DateTimeValidator extends BaseJsonValidator implements JsonValidato public DateTimeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DATETIME, validationContext); this.formatName = formatName; + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -52,7 +55,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { return errors; } diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java index abd95f980..dc24de97c 100644 --- a/src/main/java/com/networknt/schema/EnumValidator.java +++ b/src/main/java/com/networknt/schema/EnumValidator.java @@ -32,10 +32,11 @@ public class EnumValidator extends BaseJsonValidator implements JsonValidator { private final Set nodes; private final String error; + private final ValidationContext validationContext; public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext); - + this.validationContext = validationContext; if (schemaNode != null && schemaNode.isArray()) { nodes = new HashSet(); StringBuilder sb = new StringBuilder(); @@ -57,7 +58,7 @@ public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSc } // check if the parent schema declares the fields as nullable - if (config.isHandleNullableField()) { + if (validationContext.getConfig().isHandleNullableField()) { JsonNode nullable = parentSchema.getSchemaNode().get("nullable"); if (nullable != null && nullable.asBoolean()) { nodes.add(NullNode.getInstance()); @@ -83,7 +84,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); if (node.isNumber()) node = DecimalNode.valueOf(node.decimalValue()); - if (!nodes.contains(node) && !(config.isTypeLoose() && isTypeLooseContainsInEnum(node))) { + if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) { errors.add(buildValidationMessage(at, error)); } @@ -96,7 +97,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String * @param node JsonNode to check */ private boolean isTypeLooseContainsInEnum(JsonNode node) { - if (TypeFactory.getValueNodeType(node, super.config) == JsonType.STRING) { + if (TypeFactory.getValueNodeType(node, this.validationContext.getConfig()) == JsonType.STRING) { String nodeText = node.textValue(); for (JsonNode n : nodes) { String value = n.asText(); diff --git a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java index cf2942da1..b9f7d4491 100644 --- a/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java +++ b/src/main/java/com/networknt/schema/ExclusiveMaximumValidator.java @@ -30,9 +30,11 @@ public class ExclusiveMaximumValidator extends BaseJsonValidator implements Json private final ThresholdMixin typedMaximum; + private final ValidationContext validationContext; + public ExclusiveMaximumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MAXIMUM, validationContext); - + this.validationContext = validationContext; if (!schemaNode.isNumber()) { throw new JsonSchemaException("exclusiveMaximum value is not a number"); } @@ -99,7 +101,7 @@ public String thresholdValue() { public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - if (!TypeValidator.isNumber(node, super.config)) { + if (!TypeValidator.isNumber(node, validationContext.getConfig())) { // maximum only applies to numbers return Collections.emptySet(); } diff --git a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java index afccbfe14..1444a2d4c 100644 --- a/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java +++ b/src/main/java/com/networknt/schema/ExclusiveMinimumValidator.java @@ -34,9 +34,11 @@ public class ExclusiveMinimumValidator extends BaseJsonValidator implements Json */ private final ThresholdMixin typedMinimum; + private final ValidationContext validationContext; + public ExclusiveMinimumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.EXCLUSIVE_MINIMUM, validationContext); - + this.validationContext = validationContext; if (!schemaNode.isNumber()) { throw new JsonSchemaException("exclusiveMinimum value is not a number"); } @@ -106,7 +108,7 @@ public String thresholdValue() { public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - if (!TypeValidator.isNumber(node, super.config)) { + if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { // minimum only applies to numbers return Collections.emptySet(); } diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java index 6db5f0192..7e7962d71 100644 --- a/src/main/java/com/networknt/schema/FormatValidator.java +++ b/src/main/java/com/networknt/schema/FormatValidator.java @@ -29,10 +29,12 @@ public class FormatValidator extends BaseJsonValidator implements JsonValidator private static final Logger logger = LoggerFactory.getLogger(FormatValidator.class); private final Format format; + private final ValidationContext validationContext; public FormatValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, Format format) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT, validationContext); this.format = format; + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -41,7 +43,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { return errors; } diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java index 6f8ed5af0..3a81f5607 100644 --- a/src/main/java/com/networknt/schema/ItemsValidator.java +++ b/src/main/java/com/networknt/schema/ItemsValidator.java @@ -33,7 +33,7 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator { private final List tupleSchema; private boolean additionalItems = true; private final JsonSchema additionalSchema; - private final WalkListenerRunner arrayItemWalkListenerRunner; + private WalkListenerRunner arrayItemWalkListenerRunner; private final ValidationContext validationContext; public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, @@ -61,7 +61,7 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS } } } - arrayItemWalkListenerRunner = new DefaultItemWalkListenerRunner(config.getArrayItemWalkListeners()); + arrayItemWalkListenerRunner = new DefaultItemWalkListenerRunner(validationContext.getConfig().getArrayItemWalkListeners()); this.validationContext = validationContext; @@ -76,7 +76,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - if (!node.isArray() && !config.isTypeLoose()) { + if (!node.isArray() && !this.validationContext.getConfig().isTypeLoose()) { // ignores non-arrays return errors; } diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 18936b954..6799e4d40 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -19,6 +19,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; +import java.sql.Ref; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -36,6 +37,8 @@ import com.networknt.schema.walk.JsonSchemaWalker; import com.networknt.schema.walk.WalkListenerRunner; +import javax.xml.validation.Schema; + /** * This is the core of json constraint implementation. It parses json constraint * file and generates JsonValidators. The class is thread safe, once it is @@ -46,7 +49,6 @@ public class JsonSchema extends BaseJsonValidator { private Map validators; private final String idKeyword; private final ValidationContext validationContext; - private WalkListenerRunner keywordWalkListenerRunner; private boolean validatorsLoaded = false; /** @@ -80,13 +82,10 @@ private JsonSchema(ValidationContext validationContext, String schemaPath, URI c super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval, validationContext.getConfig() != null && validationContext.getConfig().isFailFast()); this.validationContext = validationContext; - this.config = validationContext.getConfig(); this.idKeyword = validationContext.getMetaSchema().getIdKeyword(); this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode); - if (config != null) { - this.keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(config.getKeywordWalkListenersMap()); - - if (config.isOpenAPI3StyleDiscriminators()) { + if (validationContext.getConfig() != null) { + if (validationContext.getConfig().isOpenAPI3StyleDiscriminators()) { ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator"); if (null != discriminator && null != validationContext.getCurrentDiscriminatorContext()) { validationContext.getCurrentDiscriminatorContext().registerDiscriminator(schemaPath, discriminator); @@ -95,6 +94,10 @@ private JsonSchema(ValidationContext validationContext, String schemaPath, URI c } } + ValidationContext getValidationContext() { + return this.validationContext; + } + private URI combineCurrentUriWithIds(URI currentUri, JsonNode schemaNode) { final String id = validationContext.resolveSchemaId(schemaNode); if (id == null) { @@ -257,6 +260,7 @@ private JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema) { /************************ START OF VALIDATE METHODS **********************************/ public Set validate(JsonNode jsonNode, JsonNode rootNode, String at) { + SchemaValidatorsConfig config = validationContext.getConfig(); Set errors = new LinkedHashSet(); // Get the collector context. getCollectorContext(); @@ -353,6 +357,7 @@ public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) { @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { Set validationMessages = new LinkedHashSet(); + WalkListenerRunner keywordWalkListenerRunner = new DefaultKeywordWalkListenerRunner(this.validationContext.getConfig().getKeywordWalkListenersMap()); // Walk through all the JSONWalker's. for (Entry entry : getValidators().entrySet()) { JsonSchemaWalker jsonWalker = entry.getValue(); @@ -401,11 +406,12 @@ private void setValidatorState(boolean isWalkEnabled, boolean shouldValidateSche } public CollectorContext getCollectorContext() { + SchemaValidatorsConfig config = validationContext.getConfig(); CollectorContext collectorContext = (CollectorContext) ThreadInfo .get(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY); if (collectorContext == null) { - if (this.config != null && this.config.getCollectorContext() != null) { - collectorContext = this.config.getCollectorContext(); + if (config != null && config.getCollectorContext() != null) { + collectorContext = config.getCollectorContext(); } else { collectorContext = new CollectorContext(); } diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java index 8fe4c5118..b8f4baf7e 100644 --- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java +++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java @@ -338,8 +338,14 @@ public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig co throw new JsonSchemaException(e); } - if (uriSchemaCache.containsKey(mappedUri)) - return uriSchemaCache.get(mappedUri); + if (uriSchemaCache.containsKey(mappedUri)) { + JsonSchema cachedUriSchema = uriSchemaCache.get(mappedUri); + // 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. + cachedUriSchema.getValidationContext().setConfig(config); + return cachedUriSchema; + } try { inputStream = this.uriFetcher.fetch(mappedUri); diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java index dd1779009..97a68123e 100644 --- a/src/main/java/com/networknt/schema/MaxItemsValidator.java +++ b/src/main/java/com/networknt/schema/MaxItemsValidator.java @@ -24,8 +24,11 @@ import java.util.Set; public class MaxItemsValidator extends BaseJsonValidator implements JsonValidator { + private static final Logger logger = LoggerFactory.getLogger(MaxItemsValidator.class); + private final ValidationContext validationContext; + private int max = 0; public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { @@ -33,7 +36,7 @@ public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema pare if (schemaNode.isIntegralNumber()) { max = schemaNode.intValue(); } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -44,7 +47,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (node.size() > max) { return Collections.singleton(buildValidationMessage(at, "" + max)); } - } else if (config.isTypeLoose()) { + } else if (this.validationContext.getConfig().isTypeLoose()) { if (1 > max) { return Collections.singleton(buildValidationMessage(at, "" + max)); } diff --git a/src/main/java/com/networknt/schema/MaxLengthValidator.java b/src/main/java/com/networknt/schema/MaxLengthValidator.java index e5c097f71..0fbe76176 100644 --- a/src/main/java/com/networknt/schema/MaxLengthValidator.java +++ b/src/main/java/com/networknt/schema/MaxLengthValidator.java @@ -26,6 +26,8 @@ public class MaxLengthValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(MaxLengthValidator.class); + private final ValidationContext validationContext; + private int maxLength; public MaxLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { @@ -34,14 +36,14 @@ public MaxLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema par if (schemaNode != null && schemaNode.isIntegralNumber()) { maxLength = schemaNode.intValue(); } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { // ignore no-string typs return Collections.emptySet(); diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java index b189bc7b0..9bd3a3438 100644 --- a/src/main/java/com/networknt/schema/MaximumValidator.java +++ b/src/main/java/com/networknt/schema/MaximumValidator.java @@ -32,11 +32,12 @@ public class MaximumValidator extends BaseJsonValidator implements JsonValidator private boolean excludeEqual = false; private final ThresholdMixin typedMaximum; + private final ValidationContext validationContext; public MaximumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext); - + this.validationContext = validationContext; if (!schemaNode.isNumber()) { throw new JsonSchemaException("maximum value is not a number"); } @@ -108,7 +109,7 @@ public String thresholdValue() { public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - if (!TypeValidator.isNumber(node, super.config)) { + if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { // maximum only applies to numbers return Collections.emptySet(); } diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java index b58c5c072..dd840469d 100644 --- a/src/main/java/com/networknt/schema/MinItemsValidator.java +++ b/src/main/java/com/networknt/schema/MinItemsValidator.java @@ -26,6 +26,8 @@ public class MinItemsValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(MinItemsValidator.class); + private final ValidationContext validationContext; + private int min = 0; public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { @@ -33,7 +35,7 @@ public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema pare if (schemaNode.isIntegralNumber()) { min = schemaNode.intValue(); } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -44,7 +46,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String if (node.size() < min) { return Collections.singleton(buildValidationMessage(at, "" + min)); } - } else if (config.isTypeLoose()) { + } else if (this.validationContext.getConfig().isTypeLoose()) { if (1 < min) { return Collections.singleton(buildValidationMessage(at, "" + min)); } diff --git a/src/main/java/com/networknt/schema/MinLengthValidator.java b/src/main/java/com/networknt/schema/MinLengthValidator.java index cec85eb43..af88dd463 100644 --- a/src/main/java/com/networknt/schema/MinLengthValidator.java +++ b/src/main/java/com/networknt/schema/MinLengthValidator.java @@ -26,6 +26,8 @@ public class MinLengthValidator extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(MinLengthValidator.class); + private final ValidationContext validationContext; + private int minLength; public MinLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { @@ -34,14 +36,14 @@ public MinLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema par if (schemaNode != null && schemaNode.isIntegralNumber()) { minLength = schemaNode.intValue(); } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { // ignore non-string types return Collections.emptySet(); diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java index f9af8f8be..2208120ea 100644 --- a/src/main/java/com/networknt/schema/MinimumValidator.java +++ b/src/main/java/com/networknt/schema/MinimumValidator.java @@ -37,6 +37,8 @@ public class MinimumValidator extends BaseJsonValidator implements JsonValidator */ private final ThresholdMixin typedMinimum; + private final ValidationContext validationContext; + public MinimumValidator(String schemaPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext); @@ -109,12 +111,13 @@ public String thresholdValue() { } }; } + this.validationContext = validationContext; } public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - if (!TypeValidator.isNumber(node, super.config)) { + if (!TypeValidator.isNumber(node, this.validationContext.getConfig())) { // minimum only applies to numbers return Collections.emptySet(); } diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index b44f5dd80..283875e56 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -27,13 +27,17 @@ import java.util.*; public class OneOfValidator extends BaseJsonValidator implements JsonValidator { + private static final Logger logger = LoggerFactory.getLogger(OneOfValidator.class); private final List schemas = new ArrayList(); + private final ValidationContext validationContext; + private static class ShortcutValidator { private final JsonSchema schema; private final Map constants; + private final ValidationContext validationContext; ShortcutValidator(JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, JsonSchema schema) { @@ -41,6 +45,7 @@ private static class ShortcutValidator { JsonSchema resolvedRefSchema = refNode != null && refNode.isTextual() ? RefValidator.getRefSchema(parentSchema, validationContext, refNode.textValue()).getSchema() : null; this.constants = extractConstants(schemaNode, resolvedRefSchema); this.schema = schema; + this.validationContext = validationContext; } private Map extractConstants(JsonNode schemaNode, JsonSchema resolvedRefSchema) { @@ -125,7 +130,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), childNode, parentSchema); schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema)); } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -159,7 +164,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String }*/ //Check to see if it is already validated. - if(!childErrors.isEmpty() && JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, super.config))){ + if(!childErrors.isEmpty() && JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, this.validationContext.getConfig()))){ continue; } @@ -167,7 +172,8 @@ public Set validate(JsonNode node, JsonNode rootNode, String JsonSchema schema = validator.schema; //Skip the validation when the current node is oneOf type and it is not equal to schemaType. - if(JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, super.config)) && !JsonNodeUtil.equalsToSchemaType(node,schema,config) && !(JsonType.UNKNOWN.equals(JsonNodeUtil.getSchemaJsonType(schema)))){ + if (JsonNodeUtil.matchOneOfTypeNode(schemaNode, TypeFactory.getValueNodeType(node, this.validationContext.getConfig())) && + !JsonNodeUtil.equalsToSchemaType(node, schema, this.validationContext.getConfig()) && !(JsonType.UNKNOWN.equals(JsonNodeUtil.getSchemaJsonType(schema)))) { continue; } @@ -189,9 +195,11 @@ public Set validate(JsonNode node, JsonNode rootNode, String childErrors.addAll(schemaErrors); } // ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1. - if(numberOfValidSchema > 1){ + if (numberOfValidSchema > 1) { // check if the parent schema declares the fields as nullable - if (!JsonType.NULL.equals(TypeFactory.getValueNodeType(node,config)) || !JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode(),config) && !JsonNodeUtil.isChildNodeNullable((ArrayNode) schemaNode,config)) { + if (!JsonType.NULL.equals(TypeFactory.getValueNodeType(node, this.validationContext.getConfig())) || + !JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode(), this.validationContext.getConfig()) && + !JsonNodeUtil.isChildNodeNullable((ArrayNode) schemaNode, this.validationContext.getConfig())) { final ValidationMessage message = getMultiSchemasValidErrorMsg(at); if (failFast) { throw new JsonSchemaException(message); diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java index 36a74077a..548b2a82f 100644 --- a/src/main/java/com/networknt/schema/PatternValidator.java +++ b/src/main/java/com/networknt/schema/PatternValidator.java @@ -35,7 +35,10 @@ public class PatternValidator implements JsonValidator { private final JsonValidator delegate; + private final ValidationContext validationContext; + public PatternValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { + this.validationContext = validationContext; if (validationContext.getConfig() != null && validationContext.getConfig().isEcma262Validator()) { delegate = new PatternValidatorEcma262(schemaPath, schemaNode, parentSchema, validationContext); } else { @@ -60,7 +63,7 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, private static class PatternValidatorJava extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class); - + private final ValidationContext validationContext; private String pattern; private Pattern compiledPattern; @@ -77,7 +80,7 @@ public PatternValidatorJava(String schemaPath, JsonNode schemaNode, JsonSchema p throw pse; } } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -88,7 +91,7 @@ private boolean matches(String value) { public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { return Collections.emptySet(); } @@ -107,7 +110,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String private static class PatternValidatorEcma262 extends BaseJsonValidator implements JsonValidator { private static final Logger logger = LoggerFactory.getLogger(PatternValidator.class); - + private final ValidationContext validationContext; private String pattern; private Regex compiledRegex; @@ -124,7 +127,7 @@ public PatternValidatorEcma262(String schemaPath, JsonNode schemaNode, JsonSchem throw se; } } - + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -145,7 +148,7 @@ private boolean matches(String value) { public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); if (nodeType != JsonType.STRING) { return Collections.emptySet(); } diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java index 16b6adc77..17c201664 100644 --- a/src/main/java/com/networknt/schema/PropertiesValidator.java +++ b/src/main/java/com/networknt/schema/PropertiesValidator.java @@ -28,7 +28,6 @@ public class PropertiesValidator extends BaseJsonValidator implements JsonValida public static final String PROPERTY = "properties"; private static final Logger logger = LoggerFactory.getLogger(PropertiesValidator.class); private final Map schemas = new HashMap(); - private final WalkListenerRunner propertyWalkListenerRunner; private final ValidationContext validationContext; public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { @@ -38,12 +37,13 @@ public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema pa String pname = it.next(); schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, parentSchema.getCurrentUri(), schemaNode.get(pname), parentSchema)); } - propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(config.getPropertyWalkListeners()); } public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); + WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(this.validationContext.getConfig().getPropertyWalkListeners()); + Set errors = new LinkedHashSet(); // get the Validator state object storing validation data @@ -68,7 +68,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String errors.addAll(propertySchema.validate(propertyNode, rootNode, at + "." + entry.getKey())); } else { // check if walker is enabled. If it is enabled it is upto the walker implementation to decide about the validation. - walkSchema(entry, node, rootNode, at, state.isValidationEnabled(), errors); + walkSchema(entry, node, rootNode, at, state.isValidationEnabled(), errors, propertyWalkListenerRunner); } // reset the complex flag to the original value before the recursive call @@ -105,15 +105,16 @@ public Set walk(JsonNode node, JsonNode rootNode, String at, if (shouldValidateSchema) { validationMessages.addAll(validate(node, rootNode, at)); } else { + WalkListenerRunner propertyWalkListenerRunner = new DefaultPropertyWalkListenerRunner(this.validationContext.getConfig().getPropertyWalkListeners()); for (Map.Entry entry : schemas.entrySet()) { - walkSchema(entry, node, rootNode, at, shouldValidateSchema, validationMessages); + walkSchema(entry, node, rootNode, at, shouldValidateSchema, validationMessages, propertyWalkListenerRunner); } } return validationMessages; } private void walkSchema(Map.Entry entry, JsonNode node, JsonNode rootNode, String at, - boolean shouldValidateSchema, Set validationMessages) { + boolean shouldValidateSchema, Set validationMessages, WalkListenerRunner propertyWalkListenerRunner) { JsonSchema propertySchema = entry.getValue(); JsonNode propertyNode = (node == null ? null : node.get(entry.getKey())); boolean executeWalk = propertyWalkListenerRunner.runPreWalkListeners(ValidatorTypeCode.PROPERTIES.getValue(), diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java index b08b9864e..74d2f73fa 100644 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ b/src/main/java/com/networknt/schema/RefValidator.java @@ -34,11 +34,14 @@ public class RefValidator extends BaseJsonValidator implements JsonValidator { protected JsonSchemaRef schema; + private JsonSchema parentSchema; + private static final String REF_CURRENT = "#"; public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext); String refValue = schemaNode.asText(); + this.parentSchema = parentSchema; schema = getRefSchema(parentSchema, validationContext, refValue); if (schema == null) { throw new JsonSchemaException( @@ -128,7 +131,10 @@ private static URI determineSchemaUrn(final URNFactory urnFactory, final String public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - + // 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.getSchema().getValidationContext().setConfig(parentSchema.getValidationContext().getConfig()); if (schema != null) { return schema.validate(node, rootNode, at); } else { @@ -138,6 +144,10 @@ public Set validate(JsonNode node, JsonNode rootNode, String @Override public Set walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) { + // 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.getSchema().getValidationContext().setConfig(parentSchema.getValidationContext().getConfig()); if (schema != null) { return schema.walk(node, rootNode, at, shouldValidateSchema); } diff --git a/src/main/java/com/networknt/schema/TypeFactory.java b/src/main/java/com/networknt/schema/TypeFactory.java index 3e0693d23..71bb8ea5f 100644 --- a/src/main/java/com/networknt/schema/TypeFactory.java +++ b/src/main/java/com/networknt/schema/TypeFactory.java @@ -72,9 +72,9 @@ public static JsonType getValueNodeType(JsonNode node, SchemaValidatorsConfig co if (node.isIntegralNumber()) return JsonType.INTEGER; if (node.isNumber()) - if (config.isJavaSemantics() && node.canConvertToLong() && (node.asText().indexOf('.') == -1)) + if (config != null && config.isJavaSemantics() && node.canConvertToLong() && (node.asText().indexOf('.') == -1)) return JsonType.INTEGER; - else if (config.isLosslessNarrowing() && node.asText().endsWith(".0")) + else if (config!= null && config.isLosslessNarrowing() && node.asText().endsWith(".0")) return JsonType.INTEGER; else return JsonType.NUMBER; diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java index 8ca71732a..50950830e 100644 --- a/src/main/java/com/networknt/schema/TypeValidator.java +++ b/src/main/java/com/networknt/schema/TypeValidator.java @@ -37,12 +37,13 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator { private JsonType schemaType; private JsonSchema parentSchema; private UnionTypeValidator unionTypeValidator; + private final ValidationContext validationContext; public TypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext); schemaType = TypeFactory.getSchemaNodeType(schemaNode); this.parentSchema = parentSchema; - + this.validationContext = validationContext; if (schemaType == JsonType.UNION) { unionTypeValidator = new UnionTypeValidator(schemaPath, schemaNode, parentSchema, validationContext); } @@ -55,7 +56,7 @@ public JsonType getSchemaType() { } public boolean equalsToSchemaType(JsonNode node) { - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); // in the case that node type is not the same as schema type, try to convert node to the // same type of schema. In REST API, query parameters, path parameters and headers are all // string type and we must convert, otherwise, all schema validations will fail. @@ -70,7 +71,8 @@ public boolean equalsToSchemaType(JsonNode node) { ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY); if(JsonType.NULL.equals(nodeType) ){ - if((state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), config)) || JsonNodeUtil.isNodeNullable(this.getParentSchema().getSchemaNode()) ){ + if ((state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), validationContext.getConfig())) || + JsonNodeUtil.isNodeNullable(this.getParentSchema().getSchemaNode())) { return true; } } @@ -80,7 +82,7 @@ public boolean equalsToSchemaType(JsonNode node) { if (isEnumObjectSchema(parentSchema)) { return true; } - if (config.isTypeLoose()) { + if (validationContext.getConfig().isTypeLoose()) { // if typeLoose is true, everything can be a size 1 array if (schemaType == JsonType.ARRAY) { return true; @@ -115,8 +117,8 @@ public Set validate(JsonNode node, JsonNode rootNode, String } //if (!equalsToSchemaType(node)) { - if(!JsonNodeUtil.equalsToSchemaType(node,schemaType, parentSchema, super.config)){ - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + if(!JsonNodeUtil.equalsToSchemaType(node,schemaType, parentSchema, validationContext.getConfig())){ + JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); return Collections.singleton(buildValidationMessage(at, nodeType.toString(), schemaType.toString())); } return Collections.emptySet(); diff --git a/src/main/java/com/networknt/schema/UUIDValidator.java b/src/main/java/com/networknt/schema/UUIDValidator.java index 2677befa7..1bb980c47 100644 --- a/src/main/java/com/networknt/schema/UUIDValidator.java +++ b/src/main/java/com/networknt/schema/UUIDValidator.java @@ -31,11 +31,13 @@ public class UUIDValidator extends BaseJsonValidator implements JsonValidator { private static final String regex = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"; + private final ValidationContext validationContext; public UUIDValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UUID, validationContext); this.formatName = formatName; parseErrorCode(getValidatorType().getErrorCodeKey()); + this.validationContext = validationContext; } public Set validate(JsonNode node, JsonNode rootNode, String at) { @@ -43,7 +45,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { return errors; } diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java index 547eafe26..cb7326de5 100644 --- a/src/main/java/com/networknt/schema/UnionTypeValidator.java +++ b/src/main/java/com/networknt/schema/UnionTypeValidator.java @@ -31,9 +31,11 @@ public class UnionTypeValidator extends BaseJsonValidator implements JsonValidat private final List schemas = new ArrayList(); private final String error; + private final ValidationContext validationContext; + public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE, validationContext); - + this.validationContext = validationContext; StringBuilder errorBuilder = new StringBuilder(); String sep = ""; @@ -64,7 +66,7 @@ public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema par public Set validate(JsonNode node, JsonNode rootNode, String at) { debug(logger, node, rootNode, at); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, validationContext.getConfig()); boolean valid = false; diff --git a/src/main/java/com/networknt/schema/ValidationContext.java b/src/main/java/com/networknt/schema/ValidationContext.java index a7d247497..150cd51e3 100644 --- a/src/main/java/com/networknt/schema/ValidationContext.java +++ b/src/main/java/com/networknt/schema/ValidationContext.java @@ -74,6 +74,9 @@ public JsonSchemaFactory getJsonSchemaFactory() { } public SchemaValidatorsConfig getConfig() { + if (config == null) { + config = new SchemaValidatorsConfig(); + } return config; } diff --git a/src/main/java/com/networknt/schema/format/EmailValidator.java b/src/main/java/com/networknt/schema/format/EmailValidator.java index 57e920765..e69f18301 100644 --- a/src/main/java/com/networknt/schema/format/EmailValidator.java +++ b/src/main/java/com/networknt/schema/format/EmailValidator.java @@ -62,6 +62,8 @@ public class EmailValidator extends BaseJsonValidator implements JsonValidator { private final boolean allowTld = false; private final String formatName; + private final ValidationContext validationContext; + /** *

Checks if a field has a valid e-mail address.

* @@ -138,6 +140,7 @@ protected boolean isValidUser(String user) { public EmailValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT, validationContext); this.formatName = formatName; + this.validationContext = validationContext; parseErrorCode(getValidatorType().getErrorCodeKey()); } @@ -147,7 +150,7 @@ public Set validate(JsonNode node, JsonNode rootNode, String Set errors = new LinkedHashSet(); - JsonType nodeType = TypeFactory.getValueNodeType(node, super.config); + JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig()); if (nodeType != JsonType.STRING) { return errors; } diff --git a/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java b/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java index 141c6f23d..d9ddd804f 100644 --- a/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java +++ b/src/main/java/com/networknt/schema/utils/JsonNodeUtil.java @@ -95,7 +95,7 @@ public static boolean equalsToSchemaType(JsonNode node, JsonType schemaType, Jso if (isEnumObjectSchema(parentSchema)) { return true; } - if (config.isTypeLoose()) { + if (config != null && config.isTypeLoose()) { // if typeLoose is true, everything can be a size 1 array if (schemaType == JsonType.ARRAY) { return true; diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index 8137beeb8..bacfb93a3 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -23,6 +23,8 @@ public class JsonWalkTest { private JsonSchema jsonSchema; + private JsonSchema jsonSchema1; + private static final String SAMPLE_WALK_COLLECTOR_TYPE = "sampleWalkCollectorType"; private static final String CUSTOM_KEYWORD = "custom-keyword"; @@ -39,6 +41,7 @@ public void cleanup() { private void setupSchema() { final JsonMetaSchema metaSchema = getJsonMetaSchema(); + // Create Schema. SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig(); schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); @@ -48,6 +51,13 @@ private void setupSchema() { .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)).addMetaSchema(metaSchema) .build(); this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); + // Create another Schema. + SchemaValidatorsConfig schemaValidatorsConfig1 = new SchemaValidatorsConfig(); + schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); + schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); + schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), + new PropertiesKeywordListener()); + this.jsonSchema1 = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig1); } private JsonMetaSchema getJsonMetaSchema() { @@ -75,6 +85,25 @@ public void testWalk() throws IOException { + "}"))); } + @Test + public void testWalkWithMultipleSchemas() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + ValidationResult result = jsonSchema.walk( + objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); + JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); + assertEquals(collectedNode, (objectMapper.readTree("{" + + " \"PROPERTY1\": \"sample1\"," + + " \"PROPERTY2\": \"sample2\"," + + " \"property3\": {" + + " \"street_address\":\"test-address\"," + + " \"phone_number\": {" + + " \"country-code\": \"091\"," + + " \"number\": \"123456789\"" + + " }" + + " }" + + "}"))); + } + private InputStream getSchema() { return getClass().getClassLoader().getResourceAsStream("schema/walk-schema.json"); } From d87b8ac30ac1c594a5ea68a5708e2bd7aab1efe9 Mon Sep 17 00:00:00 2001 From: Prashanth Josyula Date: Thu, 2 Sep 2021 19:03:55 +0530 Subject: [PATCH 14/14] Adding tests for changing listeners --- .../com/networknt/schema/JsonWalkTest.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/networknt/schema/JsonWalkTest.java b/src/test/java/com/networknt/schema/JsonWalkTest.java index bacfb93a3..a8dc89c5b 100644 --- a/src/test/java/com/networknt/schema/JsonWalkTest.java +++ b/src/test/java/com/networknt/schema/JsonWalkTest.java @@ -53,9 +53,8 @@ private void setupSchema() { this.jsonSchema = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig); // Create another Schema. SchemaValidatorsConfig schemaValidatorsConfig1 = new SchemaValidatorsConfig(); - schemaValidatorsConfig.addKeywordWalkListener(new AllKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); - schemaValidatorsConfig.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), + schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.REF.getValue(), new RefKeywordListener()); + schemaValidatorsConfig1.addKeywordWalkListener(ValidatorTypeCode.PROPERTIES.getValue(), new PropertiesKeywordListener()); this.jsonSchema1 = schemaFactory.getSchema(getSchema(), schemaValidatorsConfig1); } @@ -86,8 +85,9 @@ public void testWalk() throws IOException { } @Test - public void testWalkWithMultipleSchemas() throws IOException { + public void testWalkWithDifferentListeners() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); + // This instance of schema contains all listeners. ValidationResult result = jsonSchema.walk( objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); JsonNode collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); @@ -102,6 +102,21 @@ public void testWalkWithMultipleSchemas() throws IOException { + " }" + " }" + "}"))); + // This instance of schema contains one listener removed. + CollectorContext collectorContext = result.getCollectorContext(); + collectorContext.reset(); + result = jsonSchema1.walk( + objectMapper.readTree(getClass().getClassLoader().getResourceAsStream("data/walk-data.json")), false); + collectedNode = (JsonNode) result.getCollectorContext().get(SAMPLE_WALK_COLLECTOR_TYPE); + assertEquals(collectedNode, (objectMapper.readTree("{" + + " \"property3\": {" + + " \"street_address\":\"test-address\"," + + " \"phone_number\": {" + + " \"country-code\": \"091\"," + + " \"number\": \"123456789\"" + + " }" + + " }" + + "}"))); } private InputStream getSchema() {