-
Notifications
You must be signed in to change notification settings - Fork 399
Validation messages
This page describes the current status of the validation message "API" as it currently stands in the implementation.
This page exists for two reasons:
- the API is there, but there is no Javadoc for it;
- I ask for ideas.
There are three classes implied:
- ValidationReport: as its name says;
- ValidationMessage, an individual message of a validation report;
- ValidationDomain: what validation domain does this message apply to (see below).
For the recall, a ValidationReport is what you get out of a single instance validation using a JsonSchema instance. Prior to this development, all messages you added into a validation report were plain strings (and not very informative as that). Now, however,you cannot do that anymore and need to use ValidationMessage instances.
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. This is a justification for the above, basically: for instance, you have a schema with a dependencies keyword; if there is a syntax error for that keyword in your schema, the domain will be syntax validation; if an instance fails to validate against an existing dependencies keyword entry, the domain will be instance validation.
- The associated message. Of course, you want to explicitly tell why this message was there in the first place, right?
So, there. The validation domain is set by the builder constructor, you don't have a choice (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
}
}
}