Skip to content

Commit

Permalink
Fix issues with hierarchy output report (#947)
Browse files Browse the repository at this point in the history
* Fix anyOf short circuit behavior when annotation collection is on

* Fixes for the hierarchical output due to the evaluation path and instance location

* Refactor

* Fix propertyNames messages

* Fix min max contains messages

* Fix hierarchical report

* Add test for propertyNames

* Fix additionalItems

* Fix additionalItems

* Refactor

* Add test

* Fix

* Revert due to encoding

* Fix

* Fix

* Fix boolean reason

* add to schema ref

* Fix walk runners

* Fix

* Add option to disable preload

* refactor

* optimise fail fast

* Allow fail fast to drop oneOf child errors

* Remove unused field

* Fix message for unevaluatedProperties on fail fast

* fix messages
  • Loading branch information
justin-tay authored Feb 4, 2024
1 parent 91385c2 commit 85d642b
Show file tree
Hide file tree
Showing 105 changed files with 1,237 additions and 532 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
errors = new LinkedHashSet<>();
}
errors.add(message().instanceNode(node).property(pname)
.instanceLocation(instanceLocation.append(pname))
.instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(pname).build());
.failFast(executionContext.isFailFast()).arguments(pname).build());
} else {
if (additionalPropertiesSchema != null) {
ValidatorState state = executionContext.getValidatorState();
Expand Down
29 changes: 23 additions & 6 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
int numberOfValidSubSchemas = 0;
try {
// Save flag as nested schema evaluation shouldn't trigger fail fast
boolean failFast = executionContext.getExecutionConfig().isFailFast();
boolean failFast = executionContext.isFailFast();
try {
executionContext.getExecutionConfig().setFailFast(false);
executionContext.setFailFast(false);
for (JsonSchema schema : this.schemas) {
Set<ValidationMessage> errors = Collections.emptySet();
state.setMatchedNode(initialHasMatchedNode);
Expand Down Expand Up @@ -104,7 +104,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}

if (errors.isEmpty() && (!this.validationContext.getConfig().isOpenAPI3StyleDiscriminators())
&& canShortCircuit()) {
&& canShortCircuit() && canShortCircuit(executionContext)) {
// Clear all errors.
allErrors.clear();
// return empty errors.
Expand All @@ -115,7 +115,7 @@ && canShortCircuit()) {
allErrors.addAll(errors);
allErrors.add(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast())
.failFast(executionContext.isFailFast())
.arguments(DISCRIMINATOR_REMARK).build());
} else {
// Clear all errors.
Expand All @@ -128,7 +128,7 @@ && canShortCircuit()) {
}
} finally {
// Restore flag
executionContext.getExecutionConfig().setFailFast(failFast);
executionContext.setFailFast(failFast);
}

// determine only those errors which are NOT of type "required" property missing
Expand Down Expand Up @@ -174,7 +174,24 @@ public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode n
}
return new LinkedHashSet<>();
}


/**
* If annotation collection is enabled cannot short circuit.
*
* @see <a href=
* "https://github.com/json-schema-org/json-schema-spec/blob/f8967bcbc6cee27753046f63024b55336a9b1b54/jsonschema-core.md?plain=1#L1717-L1720">anyOf</a>
* @param executionContext the execution context
* @return true if can short circuit
*/
protected boolean canShortCircuit(ExecutionContext executionContext) {
return !executionContext.getExecutionConfig().isAnnotationCollectionEnabled();
}

/**
* If annotations are require for evaluation cannot short circuit.
*
* @return true if can short circuit
*/
protected boolean canShortCircuit() {
if (this.canShortCircuit == null) {
boolean canShortCircuit = true;
Expand Down
13 changes: 1 addition & 12 deletions src/main/java/com/networknt/schema/BaseJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@

public abstract class BaseJsonValidator extends ValidationMessageHandler implements JsonValidator {
protected final boolean suppressSubSchemaRetrieval;
protected final ApplyDefaultsStrategy applyDefaultsStrategy;
private final PathType pathType;

protected final JsonNode schemaNode;

Expand All @@ -59,13 +57,6 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
this.validationContext = validationContext;
this.schemaNode = schemaNode;
this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
this.applyDefaultsStrategy = (validationContext != null && validationContext.getConfig() != null
&& validationContext.getConfig().getApplyDefaultsStrategy() != null)
? validationContext.getConfig().getApplyDefaultsStrategy()
: ApplyDefaultsStrategy.EMPTY_APPLY_DEFAULTS_STRATEGY;
this.pathType = (validationContext != null && validationContext.getConfig() != null
&& validationContext.getConfig().getPathType() != null) ? validationContext.getConfig().getPathType()
: PathType.DEFAULT;
}

/**
Expand All @@ -76,8 +67,6 @@ public BaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationP
protected BaseJsonValidator(BaseJsonValidator copy) {
super(copy);
this.suppressSubSchemaRetrieval = copy.suppressSubSchemaRetrieval;
this.applyDefaultsStrategy = copy.applyDefaultsStrategy;
this.pathType = copy.pathType;
this.schemaNode = copy.schemaNode;
this.validationContext = copy.validationContext;
}
Expand Down Expand Up @@ -307,7 +296,7 @@ protected void preloadJsonSchemas(final Collection<JsonSchema> schemas) {
* @return The path.
*/
protected JsonNodePath atRoot() {
return new JsonNodePath(this.pathType);
return new JsonNodePath(this.validationContext.getConfig().getPathType());
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/ConstValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(schemaNode.asText())
.failFast(executionContext.isFailFast()).arguments(schemaNode.asText())
.build());
}
} else if (!schemaNode.equals(node)) {
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/networknt/schema/ContainsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
List<Integer> indexes = new ArrayList<>(); // for the annotation
if (null != this.schema && node.isArray()) {
// Save flag as nested schema evaluation shouldn't trigger fail fast
boolean failFast = executionContext.getExecutionConfig().isFailFast();
boolean failFast = executionContext.isFailFast();
try {
executionContext.getExecutionConfig().setFailFast(false);
executionContext.setFailFast(false);
for (JsonNode n : node) {
JsonNodePath path = instanceLocation.append(i);
if (this.schema.validate(executionContext, n, rootNode, path).isEmpty()) {
Expand All @@ -93,7 +93,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
} finally {
// Restore flag
executionContext.getExecutionConfig().setFailFast(failFast);
executionContext.setFailFast(failFast);
}
int m = 1; // default to 1 if "min" not specified
if (this.min != null) {
Expand All @@ -102,13 +102,13 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (actual < m) {
results = boundsViolated(isMinV201909 ? ValidatorTypeCode.MIN_CONTAINS : ValidatorTypeCode.CONTAINS,
executionContext.getExecutionConfig().getLocale(),
executionContext.getExecutionConfig().isFailFast(), node, instanceLocation, m);
executionContext.isFailFast(), node, instanceLocation, m);
}

if (this.max != null && actual > this.max) {
results = boundsViolated(isMinV201909 ? ValidatorTypeCode.MAX_CONTAINS : ValidatorTypeCode.CONTAINS,
executionContext.getExecutionConfig().getLocale(),
executionContext.getExecutionConfig().isFailFast(), node, instanceLocation, this.max);
executionContext.isFailFast(), node, instanceLocation, this.max);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (!matches(node.asText())) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(this.contentEncoding)
.failFast(executionContext.isFailFast()).arguments(this.contentEncoding)
.build());
}
return Collections.emptySet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (!matches(node.asText())) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(this.contentMediaType)
.failFast(executionContext.isFailFast()).arguments(this.contentMediaType)
.build());
}
return Collections.emptySet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (node.get(field) == null) {
errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast())
.failFast(executionContext.isFailFast())
.arguments(propertyDeps.toString()).build());
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/DependentRequired.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (node.get(field) == null) {
errors.add(message().instanceNode(node).property(pname).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(field, pname)
.failFast(executionContext.isFailFast()).arguments(field, pname)
.build());
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/EnumValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (!nodes.contains(node) && !( this.validationContext.getConfig().isTypeLoose() && isTypeLooseContainsInEnum(node))) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).arguments(error).build());
.failFast(executionContext.isFailFast()).arguments(error).build());
}

return Collections.emptySet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (typedMaximum.crossesThreshold(node)) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast())
.failFast(executionContext.isFailFast())
.arguments(typedMaximum.thresholdValue()).build());
}
return Collections.emptySet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (typedMinimum.crossesThreshold(node)) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast())
.failFast(executionContext.isFailFast())
.arguments(typedMinimum.thresholdValue()).build());
}
return Collections.emptySet();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/ExecutionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class ExecutionConfig {
* Determine if the validation execution can fail fast.
*/
private boolean failFast = false;

/**
* Gets the locale to use for formatting messages.
*
Expand Down
34 changes: 33 additions & 1 deletion src/main/java/com/networknt/schema/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public class ExecutionContext {
private Stack<DiscriminatorContext> discriminatorContexts = new Stack<>();
private JsonNodeAnnotations annotations = new JsonNodeAnnotations();
private JsonNodeResults results = new JsonNodeResults();

/**
* This is used during the execution to determine if the validator should fail fast.
* <p>
* This valid is determined by the previous validator.
*/
private Boolean failFast = null;

/**
* Creates an execution context.
Expand Down Expand Up @@ -111,7 +118,32 @@ public JsonNodeAnnotations getAnnotations() {
public JsonNodeResults getResults() {
return results;
}


/**
* Determines if the validator should immediately throw a fail fast exception if
* an error has occurred.
* <p>
* This defaults to the execution config fail fast at the start of the execution.
*
* @return true if fail fast
*/
public boolean isFailFast() {
if (this.failFast == null) {
this.failFast = getExecutionConfig().isFailFast();
}
return failFast;
}

/**
* Sets if the validator should immediately throw a fail fast exception if an
* error has occurred.
*
* @param failFast true to fail fast
*/
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}

/**
* Gets the validator state.
*
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/networknt/schema/FalseValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@
public class FalseValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(FalseValidator.class);

private final String reason;

public FalseValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, final JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaLocation, evaluationPath, schemaNode, parentSchema, ValidatorTypeCode.FALSE, validationContext);
this.reason = this.evaluationPath.getParent().getName(-1);
}

public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
debug(logger, node, rootNode, instanceLocation);
// For the false validator, it is always not valid
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast()).build());
.failFast(executionContext.isFailFast()).arguments(reason).build());
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/FormatValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
// leading and trailing spaces
errors.add(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.getExecutionConfig().isFailFast())
.failFast(executionContext.isFailFast())
.arguments(format.getName(), format.getErrorMessageDescription()).build());
}
} else if(node.textValue().contains("%")) {
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/networknt/schema/IfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
boolean ifConditionPassed = false;

// Save flag as nested schema evaluation shouldn't trigger fail fast
boolean failFast = executionContext.getExecutionConfig().isFailFast();
boolean failFast = executionContext.isFailFast();
try {
executionContext.getExecutionConfig().setFailFast(false);
executionContext.setFailFast(false);
ifConditionPassed = this.ifSchema.validate(executionContext, node, rootNode, instanceLocation).isEmpty();
} finally {
// Restore flag
executionContext.getExecutionConfig().setFailFast(failFast);
executionContext.setFailFast(failFast);
}

if (ifConditionPassed && this.thenSchema != null) {
Expand Down
Loading

0 comments on commit 85d642b

Please sign in to comment.