Skip to content

Validation messages

fge edited this page Aug 30, 2012 · 12 revisions

Introduction

Purpose

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.

Overview

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).

Using the ValidationMessage class

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).

Mandatory fields

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!

Adding further information

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() ;)

User API

Legacy: getting hold of all messages as plain strings

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()).

New: getting hold of all messages as JSON

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
    }

More?

Here is one point for which I'd appreciate feedback ;)

Developer API

Reporting errors in sytax checkers

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());
    }

Reporting errors in keyword validators

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)

Current JSON Schema for one validation message

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
            }
        }
    }