-
Notifications
You must be signed in to change notification settings - Fork 400
Validation messages
It is sometimes necessary to do more with JSON Schema validation than just validating whether an instance is valid. One may want, for instance, to collect the list of error messages and present them to the user.
Up until now, this API could not do that. This is being addressed by this development, which is scheduled for version 0.6. This development chooses JSON itself as the medium of choice for message retrieval.
This page describes the current status of the validation message API. Right now, this is the only reference document as Javadoc for this API is still missing.
There are three classes implied:
- ValidationReport: the result of one instance validation;
- ValidationMessage, an individual message of a validation report;
- ValidationDomain: what validation domain does this message apply to (see below).
Like a few other classes in this API, you cannot instantiate one directly but have to use its builtin Builder class (yeah, I am probably overusing this pattern). All methods mentioned below are Builder methods. Use .build() to actually build the message (see examples in the next section).
A message has three fields which must be set:
- The validation domain. Four validation domains are defined: ref resolving, syntax validation, instance validation and unknown. It is of course discouraged to use the latter -- and the first of them is only really useful for $ref, but it is a critical part which deserved its own domain.
- The associated keyword. The associated schema keyword. In some rare situations, it will be set to N/A.
- The associated message. This should inform the user of the nature of the validation failure.
The validation domain is set by the builder constructor (the argument is a ValidationDomain enum value). You set the associated keyword using .setKeyword() and the message using .setMessage(). Any missing required field will yield a NullPointerException when you try to .build() the message!
If you wish to add further information to a validation message, you will use .addInfo(). Its first argument is always a String. The second argument can be, in order of preference:
- a JsonNode,
- an int,
- any other object, or a collection of any object.
In the latter case, the appended information will be that object's .toString() result (one element), or a JSON Array consisting of each of the underlying objects' .toString() result (a collection).
You can see where this is going: if you want to add your own object(s) into a validation report, it had better implement .toString() ;)
ValidationReport's .getMessages() method is still there and still returns a List<String>. The difference there is that individual messages have now much more information (each message is the result of ValidationMessage's .toString()).
There is a new method which you may prefer: .asJsonNode(). This will return a single JSON Object, where keys are JSON Pointers into the instance, and values are arrays of messages. Here is an example of an individual message reporting an instance validation failure for minimum:
{
"domain": "validation",
"keyword": "minimum",
"message": "number is lower than the required minimum",
"minimum": 0,
"found": -1
}
Here is one point for which I'd appreciate feedback ;)
The prototype of the .checkSyntax() method has changed:
- before: void checkSyntax(final List<String> messages, final JsonNode schema);;
- now: void checkSyntax(final ValidationMessage.Builder msg, final List<ValidationMessage> messages, final JsonNode schema);
The provided ValidationMessage.Builder will have its validation domain (syntax) and keyword already filled for you. All you need to do is set the message, add further information if need be, and add the builder's .build() result into the report.
In fact, more often than not you'll inherit from SimpleSyntaxChecker and will have to implement checkValue() instead which has exactly the same prototype (by the time you reach this method, you are guaranteed that the type of the keyword value is correct -- see the javadoc for more details). Here is the current implementation of checkValue() for the divisibleBy syntax checker:
@Override
void checkValue(final ValidationMessage.Builder msg,
final List<ValidationMessage> messages, final JsonNode schema)
{
final JsonNode node = schema.get(keyword);
if (node.decimalValue().compareTo(ZERO) > 0)
return;
msg.setMessage("divisibleBy is not strictly greater than 0")
.addInfo("value", node);
messages.add(msg.build());
}
The prototype of the validating method has not changed. However, as mentioned above, you cannot add plain strings to a ValidationReport anymore.
There is a utility method called newMsg() which will return a pre-filled ValidationMessage.Builder for you, with the domain (instance validation) and keyword. If you need to report errors, you will then call this newMsg() method and fill your information before adding the message to the report.
Here is the implementation of the .validate() method for the properties keyword:
@Override
public void validate(final ValidationContext context,
final ValidationReport report, final JsonNode instance)
{
final Set<String> fields = JacksonUtils.fieldNames(instance);
if (fields.containsAll(required))
return;
final Set<String> requiredSorted = Sets.newTreeSet(required);
final Set<String> missing = Sets.newTreeSet(required);
missing.removeAll(fields);
final ValidationMessage.Builder msg = newMsg()
.addInfo("required", requiredSorted).addInfo("missing", missing)
.setMessage("required property(ies) not found");
report.addMessage(msg.build());
}
(you will have noted the use of Sets.newTreeSet() above: this is simply to make properties appear in alphabetical order, which is more readable in a report)
Well, this is JSON Schema after all, right? You should be able to validate messages too. Here goes:
{
"description": "schema for a ValidationMessage's JSON representation",
"type": "object",
"properties": {
"domain": {
"enum": [ "$ref resolving", "syntax", "validation", "unknown" ],
"required": true
},
"keyword": {
"type": "string",
"required": true
},
"message": {
"type": "string",
"required": true
}
}
}