Skip to content

Validation messages

fge edited this page Aug 30, 2012 · 12 revisions

Introduction

Purpose

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.

Overview

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 put into a validation report were plain strings (and not very informative as that). But now, all this has changed.

NOTE to existing users: the .getMessages() method still exists, and still returns a list of strings; but they are much more verbose than before.

NOTE to existing keyword implementors: read below, you cannot just stuff a plain string into a report anymore.

Anatomy of a validation message

As mentioned above, the class used for this is ValidationMessage. You cannot instantiate one directly but have to use its builtin builder class (yeah, I am probably overusing this pattern).

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

Adding further information

Now, you can of course add further information to the message, and are in fact strongly advised to do so. For this, the method to use (on the builder, always) is .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, or a JSON Array consisting of each of the underlying object's .toString() result. 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, and add further information if need be.

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> missing = Sets.newTreeSet(required);
        missing.removeAll(fields);

        final ValidationMessage.Builder msg = newMsg()
            .addInfo("required", required).addInfo("missing", missing)
            .setMessage("required property(ies) not found");
        report.addMessage(msg.build());
    }

Current JSON Schema for one validation message

Well, this is JSON Schema after all, right? You should be able to validate that message. So, just in case:

    {
        "type": "object",
        "properties": {
            "domain": {
                "enum": [ "$ref resolving", "syntax", "validation", "unknown" ],
                "required": true
            },
            "keyword": {
                "type": "string",
                "required": true
            },
            "message": {
                "type": "string",
                "required": true
            }
        }
    }
Clone this wiki locally