Skip to content

Commit

Permalink
Fix validation messages (#969)
Browse files Browse the repository at this point in the history
* Generate translations and ensure encoded in ISO-8859-1 for Java 8

* Make how schema errors are reported consistent

* Refactor oneOf

* Make id error message clearer

* Make messages more consistent

* Fix

* Fix

* Fix

* Fix
  • Loading branch information
justin-tay authored Feb 15, 2024
1 parent 7cda40e commit 2879ca3
Show file tree
Hide file tree
Showing 76 changed files with 2,275 additions and 1,476 deletions.
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.isFailFast()).arguments(schemaNode.asText())
.failFast(executionContext.isFailFast()).arguments(schemaNode.asText(), node.asText())
.build());
}
} else if (!schemaNode.equals(node)) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private static SchemaLocation resolve(SchemaLocation schemaLocation, JsonNode sc
ValidationMessage validationMessage = ValidationMessage.builder()
.code(ValidatorTypeCode.ID.getValue()).type(ValidatorTypeCode.ID.getValue())
.instanceLocation(idSchemaLocation.getFragment())
.arguments(schemaLocation.toString(), id)
.arguments(id, validationContext.getMetaSchema().getIdKeyword(), idSchemaLocation)
.schemaLocation(idSchemaLocation)
.schemaNode(schemaNode)
.messageFormatter(args -> validationContext.getConfig().getMessageSource().getMessage(
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/networknt/schema/MaxItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (node.size() > max) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).arguments(max).build());
.failFast(executionContext.isFailFast()).arguments(max, node.size()).build());
}
} else if (this.validationContext.getConfig().isTypeLoose()) {
if (1 > max) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).arguments(max).build());
.failFast(executionContext.isFailFast()).arguments(max, 1).build());
}
}

Expand Down
13 changes: 11 additions & 2 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
state.setComplexValidator(true);

int numberOfValidSchema = 0;
int index = 0;
SetView<ValidationMessage> childErrors = null;
List<String> indexes = null;

// Save flag as nested schema evaluation shouldn't trigger fail fast
boolean failFast = executionContext.isFailFast();
Expand All @@ -82,10 +84,15 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
continue;
}
numberOfValidSchema++;
if (indexes == null) {
indexes = new ArrayList<>();
}
indexes.add(Integer.toString(index));
}

if (numberOfValidSchema > 1 && canShortCircuit()) {
// short-circuit
// note that the short circuit means that only 2 valid schemas are reported even if could be more
break;
}

Expand All @@ -95,6 +102,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
childErrors.union(schemaErrors);
}
index++;
}
} finally {
// Restore flag
Expand All @@ -105,9 +113,10 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
// is not equal to 1.
if (numberOfValidSchema != 1) {
ValidationMessage message = message().instanceNode(node).instanceLocation(instanceLocation)
.messageKey(numberOfValidSchema > 1 ? "oneOf.indexes" : "oneOf")
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast())
.arguments(Integer.toString(numberOfValidSchema)).build();
.arguments(Integer.toString(numberOfValidSchema), numberOfValidSchema > 1 ? String.join(", ", indexes) : "").build();
if (childErrors != null) {
errors = new SetView<ValidationMessage>().union(Collections.singleton(message)).union(childErrors);
} else {
Expand Down
20 changes: 10 additions & 10 deletions src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,22 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
for (int x = validCount; x < node.size(); x++) {
// The schema is either "false" or an object schema
if (!containsEvaluated.contains(x)) {
messages.addAll(
this.schema.validate(executionContext, node.get(x), node, instanceLocation.append(x)));
if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) {
// All fails as "unevaluatedItems: false"
messages.add(message().instanceNode(node).instanceLocation(instanceLocation).arguments(x)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).build());
} else {
// Schema errors will be reported as is
messages.addAll(this.schema.validate(executionContext, node.get(x), node,
instanceLocation.append(x)));
}
evaluated = true;
}
}
}
if (messages.isEmpty()) {
valid = true;
} else {
// Report these as unevaluated paths or not matching the unevaluatedItems schema
messages = messages.stream()
.map(m -> message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.arguments(m.getInstanceLocation().getName(-1))
.failFast(executionContext.isFailFast()).build())
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
// If the "unevaluatedItems" subschema is applied to any positions within the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
evaluatedProperties.add(fieldName);
if (this.schemaNode.isBoolean() && this.schemaNode.booleanValue() == false) {
// All fails as "unevaluatedProperties: false"
messages.add(message().instanceNode(node).instanceLocation(instanceLocation.append(fieldName))
.locale(executionContext.getExecutionConfig().getLocale())
messages.add(message().instanceNode(node).instanceLocation(instanceLocation).property(fieldName)
.arguments(fieldName).locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).build());
} else {
// Schema errors will be reported as is
messages.addAll(this.schema.validate(executionContext, node.get(fieldName), node,
instanceLocation.append(fieldName)));
}
Expand All @@ -131,17 +132,6 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
} finally {
executionContext.setFailFast(failFast); // restore flag
}
if (!messages.isEmpty()) {
// Report these as unevaluated paths or not matching the unevaluatedProperties
// schema
messages = messages.stream()
.map(m -> message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.arguments(m.getInstanceLocation().getName(-1))
.property(m.getInstanceLocation().getName(-1))
.failFast(executionContext.isFailFast()).build())
.collect(Collectors.toCollection(LinkedHashSet::new));
}
executionContext.getAnnotations()
.put(JsonNodeAnnotation.builder().instanceLocation(instanceLocation).evaluationPath(this.evaluationPath)
.schemaLocation(this.schemaLocation).keyword(getKeyword()).value(evaluatedProperties).build());
Expand Down
7 changes: 2 additions & 5 deletions src/main/java/com/networknt/schema/ValidatorTypeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ enum VersionCode {
private final EnumSet<VersionFlag> versions;

VersionCode(SpecVersion.VersionFlag[] versionFlags) {
this.versions = EnumSet.noneOf(VersionFlag.class);
this.versions = EnumSet.noneOf(VersionFlag.class);
for (VersionFlag flag: versionFlags) {
this.versions.add(flag);
this.versions.add(flag);
}
}

Expand All @@ -63,12 +63,10 @@ public enum ValidatorTypeCode implements Keyword, ErrorMessageType {
CONTAINS("contains", "1043", ContainsValidator::new, VersionCode.MinV6),
CONTENT_ENCODING("contentEncoding", "1052", ContentEncodingValidator::new, VersionCode.V7),
CONTENT_MEDIA_TYPE("contentMediaType", "1053", ContentMediaTypeValidator::new, VersionCode.V7),
CROSS_EDITS("crossEdits", "1004", null, VersionCode.AllVersions),
DEPENDENCIES("dependencies", "1007", DependenciesValidator::new, VersionCode.AllVersions),
DEPENDENT_REQUIRED("dependentRequired", "1045", DependentRequired::new, VersionCode.MinV201909),
DEPENDENT_SCHEMAS("dependentSchemas", "1046", DependentSchemas::new, VersionCode.MinV201909),
DYNAMIC_REF("$dynamicRef", "1051", DynamicRefValidator::new, VersionCode.MinV202012),
EDITS("edits", "1005", null, VersionCode.AllVersions),
ENUM("enum", "1008", EnumValidator::new, VersionCode.AllVersions),
EXCLUSIVE_MAXIMUM("exclusiveMaximum", "1038", ExclusiveMaximumValidator::new, VersionCode.MinV6),
EXCLUSIVE_MINIMUM("exclusiveMinimum", "1039", ExclusiveMinimumValidator::new, VersionCode.MinV6),
Expand Down Expand Up @@ -111,7 +109,6 @@ public enum ValidatorTypeCode implements Keyword, ErrorMessageType {
UNEVALUATED_PROPERTIES("unevaluatedProperties","1047",UnevaluatedPropertiesValidator::new,VersionCode.MinV201909),
UNION_TYPE("unionType", "1030", UnionTypeValidator::new, VersionCode.AllVersions),
UNIQUE_ITEMS("uniqueItems", "1031", UniqueItemsValidator::new, VersionCode.AllVersions),
UUID("uuid", "1035", null, VersionCode.AllVersions),
WRITE_ONLY("writeOnly", "1027", WriteOnlyValidator::new, VersionCode.MinV7),
;

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/networknt/schema/i18n/Locales.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public class Locales {
/**
* The list of locale resource bundles.
*/
public static final String[] SUPPORTED_LANGUAGE_TAGS = new String[] { "ar-EG", "cs-CZ", "da-DK", "de", "fa-IR",
"fi-FI", "fr-CA", "fr", "he-IL", "hr-HR", "hu-HU", "it", "ja-JP", "ko-KR", "nb-NO", "nl-NL", "pl-PL",
"pt-BR", "ro-RO", "ru-RU", "sk-SK", "sv-SE", "th-TH", "tr-TR", "uk-UA", "vi-VN", "zh-CN", "zh-TW" };
public static final String[] SUPPORTED_LANGUAGE_TAGS = new String[] { "ar", "cs", "da", "de", "fa", "fi", "fr",
"iw", "he", "hr", "hu", "it", "ja", "ko", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sv", "th", "tr", "uk",
"vi", "zh-CN", "zh-TW" };

/**
* The supported locales.
Expand Down
30 changes: 14 additions & 16 deletions src/main/resources/jsv-messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ additionalItems = {0}: index ''{1}'' is not defined in the schema and the schema
additionalProperties = {0}: property ''{1}'' is not defined in the schema and the schema does not allow additional properties
allOf = {0}: must be valid to all the schemas {1}
anyOf = {0}: must be valid to any of the schemas {1}
const = {0}: must be the constant value {1}
const = {0}: must be the constant value ''{1}''
contains = {0}: does not contain an element that passes these validations: {2}
contains.max = {0}: must contain fewer than {1} element(s) that passes these validations: {2}
contains.max = {0}: must contain at most {1} element(s) that passes these validations: {2}
contains.min = {0}: must contain at least {1} element(s) that passes these validations: {2}
crossEdits = {0}: has an error with ''cross edits''
dependencies = {0}: has an error with dependencies {1}
dependentRequired = {0}: has a missing property "{1}" which is dependent required because "{2}" is present
dependentRequired = {0}: has a missing property ''{1}'' which is dependent required because ''{2}'' is present
dependentSchemas = {0}: has an error with dependentSchemas {1}
edits = {0}: has an error with ''edits''
enum = {0}: does not have a value in the enumeration {1}
exclusiveMaximum = {0}: must have an exclusive maximum value of {1}
exclusiveMinimum = {0}: must have an exclusive minimum value of {1}
Expand All @@ -37,23 +35,24 @@ format.hostname = {0}: does not match the {1} pattern must be a valid RFC 1123 h
format.json-pointer = {0}: does not match the {1} pattern must be a valid RFC 6901 JSON Pointer
format.relative-json-pointer = {0}: does not match the {1} pattern must be a valid IETF Relative JSON Pointer
format.unknown = {0}: has an unknown format ''{1}''
id = {0}: {1} is an invalid segment for URI {2}
id = {0}: ''{1}'' is not a valid {2}
items = {0}: index ''{1}'' is not defined in the schema and the schema does not allow additional items
maxContains = {0}: must be a non-negative integer in {1}
maxItems = {0}: must have a maximum of {1} items in the array
maxItems = {0}: must have at most {1} items but found {2}
maxLength = {0}: must be at most {1} characters long
maxProperties = {0}: must only have a maximum of {1} properties
maxProperties = {0}: must have at most {1} properties
maximum = {0}: must have a maximum value of {1}
minContains = {0}: must be a non-negative integer in {1}
minContainsVsMaxContains = {0}: minContains must less than or equal to maxContains in {1}
minItems = {0}: expected at least {1} items but found {2}
minItems = {0}: must have at least {1} items but found {2}
minLength = {0}: must be at least {1} characters long
minProperties = {0}: must have a minimum of {1} properties
minProperties = {0}: must have at least {1} properties
minimum = {0}: must have a minimum value of {1}
multipleOf = {0}: must be multiple of {1}
not = {0}: must not be valid to the schema {1}
notAllowed = {0}: property ''{1}'' is not allowed but it is in the data
oneOf = {0}: must be valid to one and only one schema, but {1} are valid
oneOf.indexes = {0}: must be valid to one and only one schema, but {1} are valid with indexes ''{2}''
pattern = {0}: does not match the regex pattern {1}
patternProperties = {0}: has some error with ''pattern properties''
prefixItems = {0}: no validator found at this index
Expand All @@ -62,11 +61,10 @@ propertyNames = {0}: property ''{1}'' name is not valid: {2}
readOnly = {0}: is a readonly field, it cannot be changed
required = {0}: required property ''{1}'' not found
type = {0}: {1} found, {2} expected
unevaluatedItems = {0}: index ''{1}'' must not be unevaluated or must match unevaluated items schema
unevaluatedProperties = {0}: property ''{1}'' must not be unevaluated
unionType = {0}: {1} found, but {2} is required
uniqueItems = {0}: the items in the array must be unique
uuid = {0}: {1} is an invalid {2}
unevaluatedItems = {0}: index ''{1}'' is not evaluated and the schema does not allow unevaluated items
unevaluatedProperties = {0}: property ''{1}'' is not evaluated and the schema does not allow unevaluated properties
unionType = {0}: {1} found, {2} expected
uniqueItems = {0}: must have only unique items in the array
writeOnly = {0}: is a write-only field, it cannot appear in the data
contentEncoding = {0}: does not match content encoding {1}
contentMediaType = {0}: is not a content media type
contentMediaType = {0}: is not a content media type
Loading

0 comments on commit 2879ca3

Please sign in to comment.