Skip to content

Fixing unevaluated properties with larger test base #544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, Jso
parseErrorCode(getValidatorType().getErrorCodeKey());
}

private void addToEvaluatedProperties(String propertyPath) {
Object evaluatedProperties = CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES);
List<String> evaluatedPropertiesList = null;
if (evaluatedProperties == null) {
evaluatedPropertiesList = new ArrayList<>();
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, evaluatedPropertiesList);
} else {
evaluatedPropertiesList = (List<String>) evaluatedProperties;
}
evaluatedPropertiesList.add(propertyPath);
}

public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
if (logger.isDebugEnabled()) debug(logger, node, rootNode, at);

Expand All @@ -73,6 +85,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
return errors;
}

// if allowAdditionalProperties is true, add all the properties as evaluated.
if (allowAdditionalProperties) {
for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
addToEvaluatedProperties(at + "." + it.next());
}
}

for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
// skip the context items
Expand Down Expand Up @@ -106,6 +125,47 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
return Collections.unmodifiableSet(errors);
}

@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
if (shouldValidateSchema) {
return validate(node, rootNode, at);
}

if (node == null || !node.isObject()) {
// ignore no object
return Collections.emptySet();
}

// Else continue walking.
for (Iterator<String> 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) {
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
if (state != null && state.isWalkEnabled()) {
additionalPropertiesSchema.walk(node.get(pname), rootNode, at + "." + pname, state.isValidationEnabled());
}
}
}
}
}
return Collections.emptySet();
}

@Override
public void preloadJsonSchema() {
if(additionalPropertiesSchema!=null) {
Expand Down
48 changes: 31 additions & 17 deletions src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@

package com.networknt.schema;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.*;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand Down Expand Up @@ -49,17 +44,35 @@ public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
debug(logger, node, rootNode, at);

Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
// get the Validator state object storing validation data
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);

Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<ValidationMessage>();

// As AllOf might contain multiple schemas take a backup of evaluatedProperties.
Object backupEvaluatedProperties = CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES);

// Make the evaluatedProperties list empty.
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, new ArrayList<>());
List<String> totalEvaluatedProperties = new ArrayList<>();

for (JsonSchema schema : schemas) {
try {
errors.addAll(schema.validate(node, rootNode, at));
// Make the evaluatedProperties list empty.
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, new ArrayList<>());

Set<ValidationMessage> localErrors = new HashSet<>();

if (!state.isWalkEnabled()) {
localErrors = schema.validate(node, rootNode, at);
} else {
localErrors = schema.walk(node, rootNode, at, true);
}

childSchemaErrors.addAll(localErrors);

// Keep Collecting total evaluated properties.
if (localErrors.isEmpty()) {
totalEvaluatedProperties.addAll((List<String>) CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES));
}

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
final Iterator<JsonNode> arrayElements = schemaNode.elements();
Expand Down Expand Up @@ -93,28 +106,29 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
}
}
} finally {
if (errors.isEmpty()) {
if (childSchemaErrors.isEmpty()) {
List<String> backupEvaluatedPropertiesList = (backupEvaluatedProperties == null ? new ArrayList<>() : (List<String>) backupEvaluatedProperties);
backupEvaluatedPropertiesList.addAll((List<String>) CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES));
backupEvaluatedPropertiesList.addAll(totalEvaluatedProperties);
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, backupEvaluatedPropertiesList);
} else {
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, backupEvaluatedProperties);
}
}
}

return Collections.unmodifiableSet(errors);
return Collections.unmodifiableSet(childSchemaErrors);
}

@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();

if (shouldValidateSchema) {
return validate(node, rootNode, at);
}
for (JsonSchema schema : schemas) {
// Walk through the schema
validationMessages.addAll(schema.walk(node, rootNode, at, shouldValidateSchema));
schema.walk(node, rootNode, at, false);
}
return Collections.unmodifiableSet(validationMessages);
return Collections.emptySet();
}

@Override
Expand Down
28 changes: 14 additions & 14 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
debug(logger, node, rootNode, at);

// get the Validator state object storing validation data
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);

if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
validationContext.enterDiscriminatorContext(this.discriminatorContext, at);
}
Expand All @@ -68,6 +71,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String

try {
for (JsonSchema schema : schemas) {
Set<ValidationMessage> errors = new HashSet<>();
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
Expand All @@ -77,7 +81,11 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
continue;
}
}
Set<ValidationMessage> errors = schema.validate(node, rootNode, at);
if (!state.isWalkEnabled()) {
errors = schema.validate(node, rootNode, at);
} else {
errors = schema.walk(node, rootNode, at, true);
}
if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())) {
// Clear all errors.
allErrors.clear();
Expand Down Expand Up @@ -125,21 +133,13 @@ private void addEvaluatedProperties(Object backupEvaluatedProperties) {

@Override
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
ArrayList<Set<ValidationMessage>> results = new ArrayList<>(schemas.size());
for (JsonSchema schema : schemas) {
results.add(schema.walk(node, rootNode, at, shouldValidateSchema));
if (shouldValidateSchema) {
return validate(node, rootNode, at);
}
if(! shouldValidateSchema) {
return new LinkedHashSet<>();
}
boolean atLeastOneValid = results.stream()
.anyMatch(Set::isEmpty);
if(atLeastOneValid) {
return new LinkedHashSet<>();
for (JsonSchema schema : schemas) {
schema.walk(node, rootNode, at, false);
}
return results.stream()
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
return new LinkedHashSet<>();
}

@Override
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/networknt/schema/IfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String

Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();

boolean ifConditionPassed;
boolean ifConditionPassed = false;
try {
try {
ifConditionPassed = ifSchema.validate(node, rootNode, at).isEmpty();
Expand Down Expand Up @@ -108,7 +108,8 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
if (errors.isEmpty()) {
List<String> backupEvaluatedPropertiesList = (backupEvaluatedProperties == null ? new ArrayList<>() : (List<String>) backupEvaluatedProperties);

if (ifEvaluatedProperties != null) {
// If the "if" keyword condition is passed then only add if properties as evaluated.
if (ifEvaluatedProperties != null && ifConditionPassed) {
backupEvaluatedPropertiesList.addAll((List<String>) ifEvaluatedProperties);
}

Expand Down
68 changes: 44 additions & 24 deletions src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,14 @@ private JsonNode getMessageNode(JsonNode schemaNode, JsonSchema parentSchema) {

@Override
public Set<ValidationMessage> validate(JsonNode node) {
Set<ValidationMessage> errors = validate(node, node, AT_ROOT);
// Process UnEvaluatedProperties after all the validators are called.
errors.addAll(processUnEvaluatedProperties(node, node, AT_ROOT, true, true));
return errors;
try {
Set<ValidationMessage> errors = validate(node, node, AT_ROOT);
return errors;
} finally {
if (validationContext.getConfig().isResetCollectorContext()) {
CollectorContext.getInstance().reset();
}
}
}

public Set<ValidationMessage> validate(JsonNode jsonNode, JsonNode rootNode, String at) {
Expand All @@ -274,6 +278,10 @@ public Set<ValidationMessage> validate(JsonNode jsonNode, JsonNode rootNode, Str
for (JsonValidator v : getValidators().values()) {
errors.addAll(v.validate(jsonNode, rootNode, at));
}

// Process UnEvaluatedProperties after all the validators are called if there are no errors.
errors.addAll(processUnEvaluatedProperties(jsonNode, rootNode, at, true, true));

if (null != config && config.isOpenAPI3StyleDiscriminators()) {
ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator");
if (null != discriminator) {
Expand Down Expand Up @@ -324,10 +332,10 @@ private ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode
SchemaValidatorsConfig config = validationContext.getConfig();
// Get the collector context from the thread local.
CollectorContext collectorContext = getCollectorContext();
// Set the walkEnabled and isValidationEnabled flag in internal validator state.
setValidatorState(false, true);
// Validate.
Set<ValidationMessage> errors = validate(jsonNode, rootNode, at);
// Validate UnEvaluatedProperties after all the validators are processed.
errors.addAll(processUnEvaluatedProperties(jsonNode, rootNode, at, true, true));
// When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors.
if (config.doLoadCollectors()) {
// Load all the data from collectors into the context.
Expand All @@ -337,7 +345,9 @@ private ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
return validationResult;
} finally {
ThreadInfo.remove(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
if (validationContext.getConfig().isResetCollectorContext()) {
CollectorContext.getInstance().reset();
}
}
}

Expand All @@ -353,24 +363,30 @@ private ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode
* @return result of ValidationResult
*/
public ValidationResult walk(JsonNode node, boolean shouldValidateSchema) {
// Get the config.
SchemaValidatorsConfig config = validationContext.getConfig();
// 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.
Set<ValidationMessage> errors = walk(node, node, AT_ROOT, shouldValidateSchema);
// When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors.
if (config.doLoadCollectors()) {
// Load all the data from collectors into the context.
collectorContext.loadCollectors();
try {
// Get the config.
SchemaValidatorsConfig config = validationContext.getConfig();
// 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.
Set<ValidationMessage> errors = walk(node, node, AT_ROOT, shouldValidateSchema);
// When walk is called in series of nested call we don't want to load the collectors every time. Leave to the API to decide when to call collectors.
if (config.doLoadCollectors()) {
// Load all the data from collectors into the context.
collectorContext.loadCollectors();
}
// Process UnEvaluatedProperties after all the validators are called.
errors.addAll(processUnEvaluatedProperties(node, node, AT_ROOT, shouldValidateSchema, false));
// Collect errors and collector context into validation result.
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
return validationResult;
} finally {
if (validationContext.getConfig().isResetCollectorContext()) {
CollectorContext.getInstance().reset();
}
}
// Process UnEvaluatedProperties after all the validators are called.
errors.addAll(processUnEvaluatedProperties(node, node, AT_ROOT, shouldValidateSchema, false));
// Collect errors and collector context into validation result.
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
return validationResult;
}

@Override
Expand Down Expand Up @@ -408,6 +424,10 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
validationMessages);
}
}
if (shouldValidateSchema) {
// Process UnEvaluatedProperties after all the validators are called if there are no errors.
validationMessages.addAll(processUnEvaluatedProperties(node, rootNode, at, true, true));
}
return validationMessages;
}

Expand Down
Loading