com.github.fge.jsonschema.report.ValidationReport Maven / Gradle / Ivy
/*
* Copyright (c) 2012, Francis Galiegue
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.github.fge.jsonschema.report;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.ref.JsonPointer;
import com.github.fge.jsonschema.util.jackson.JacksonUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Queues;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
/**
* A validation report
*
* A report is a map of {@link Message}s, with the path into the validated
* instance where the error occurred as a supplementary information.
*
* You can retrieve messages either as a list of plain strings or JSON
* (either an object or an array).
*
* @see JsonSchema#validate(JsonNode)
*/
public final class ValidationReport
{
private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory();
/**
* Message list
*/
private final ListMultimap msgMap
= ArrayListMultimap.create();
/**
* The queue of paths seen so far
*/
private final Deque paths = Queues.newArrayDeque();
/**
* The current path
*/
private JsonPointer pwd;
private boolean fatal = false;
/**
* Create a new validation report with {@link JsonPointer#empty()} as an
* instance path
*/
public ValidationReport()
{
this(JsonPointer.empty());
}
/**
* Create a new validation report with an arbitraty path
*
* @param path the JSON Pointer
*/
private ValidationReport(final JsonPointer path)
{
pwd = path;
paths.push(pwd);
}
public void pushd(final String refToken)
{
paths.push(pwd);
pwd = pwd.append(refToken);
}
public void pushd(final int index)
{
paths.push(pwd);
pwd = pwd.append(index);
}
public void popd()
{
pwd = paths.pop();
}
/**
* Add one validation message to the report
*
* The message will be added to the already existing list of messages for
* the current path.
*
* @param message the message
* @return true if the added message is fatal, or if {@link #fatal} is
* already {@code true}
*/
public boolean addMessage(final Message message)
{
if (fatal)
return true;
if (message.isFatal()) {
fatal = true;
msgMap.clear();
}
msgMap.put(pwd, message);
return fatal;
}
/**
* Add several validation messages to the report
*
* @param messages the collection of messages
*/
public void addMessages(final Collection messages)
{
for (final Message message: messages)
if (addMessage(message))
return;
}
/**
* Is this report a success?
*
* @return true if the message map is empty
*/
public boolean isSuccess()
{
return msgMap.isEmpty();
}
/**
* Was there a fatal error during validation?
*
* This implementation currently considers that all URI resolution
* failures are fatal errors.
*
* @return true if a fatal error has been encountered
*/
public boolean hasFatalError()
{
return fatal;
}
/**
* Merge with another validation report
*
* Note that if a fatal error has been encountered, only the message
* describing this fatal error will make it into the report. Other messages
* are discarded.
*
* @param other the report to merge with
*/
public void mergeWith(final ValidationReport other)
{
if (fatal)
return;
if (other.fatal) {
msgMap.clear();
fatal = true;
}
msgMap.putAll(other.msgMap);
}
/**
* Make a copy of this validation report, with an empty message map and
* the current path.
*
* @return the new report
*/
public ValidationReport copy()
{
return new ValidationReport(pwd);
}
/**
* Get a flat list of validation messages as strings
*
* One message has the form:
*
*
* "/pointer/here": message here
*
*
* The list is sorted by pointer.
*
* @return the list of messages
*/
public List getMessages()
{
final Iterable pointers
= Ordering.natural().sortedCopy(msgMap.keySet());
final ImmutableList.Builder builder = ImmutableList.builder();
List messages;
for (final JsonPointer pointer: pointers) {
messages = Ordering.natural().sortedCopy(msgMap.get(pointer));
for (final Message msg: messages)
builder.add("\"" + pointer + "\": " + msg);
}
return builder.build();
}
/**
* Retrieve all messages as a JSON object
*
* The retrieved JSON document is an object where:
*
*
* - keys are string representations of {@link JsonPointer}s,
* - values are arrays of objects where each individual object is the
* JSON representation of one message.
*
*
* Note: the returned {@link JsonNode} is mutable.
*
* @see Message#toJsonNode()
*
* @return a JSON object with all validation messages
*/
public JsonNode asJsonObject()
{
final ObjectNode ret = FACTORY.objectNode();
ArrayNode node;
List messages;
for (final JsonPointer ptr: msgMap.keySet()) {
node = FACTORY.arrayNode();
messages = Ordering.natural().sortedCopy(msgMap.get(ptr));
for (final Message message: messages)
node.add(message.toJsonNode());
ret.put(ptr.toString(), node);
}
return ret;
}
/**
* Return the list of validation messages as a JSON array
*
* This method makes its best to order validation messages correctly.
*
* Each message in the resulting array is a JSON object, with the
* contents of the {@link Message} and with an added member named {@code
* path}, which contains the path into the instance where the error has
* occurred (as a {@link JsonPointer}).
*
* @see Message#toJsonNode()
*
* @return a JSON array with all validation messages
*/
public JsonNode asJsonArray()
{
final ArrayNode ret = FACTORY.arrayNode();
final Iterable pointers
= Ordering.natural().sortedCopy(msgMap.keySet());
List messages;
ObjectNode node;
for (final JsonPointer ptr: pointers) {
messages = Ordering.natural().sortedCopy(msgMap.get(ptr));
for (final Message msg: messages) {
node = FACTORY.objectNode().put("path", ptr.toString());
// I hate to do that...
node.putAll((ObjectNode) msg.toJsonNode());
ret.add(node);
}
}
return ret;
}
@Override
public String toString()
{
return "current path: \"" + pwd + "\"; " + msgMap.size() + " messages";
}
}