All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.everit.json.schema.ValidationException Maven / Gradle / Ivy

/*
 * Copyright (C) 2011 Everit Kft. (http://www.everit.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.everit.json.schema;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static java.util.Objects.requireNonNull;

/**
 * Thrown by {@link Schema} subclasses on validation failure.
 */
public class ValidationException extends RuntimeException {
    private static final long serialVersionUID = 6192047123024651924L;

    private static int getViolationCount(final List causes) {
        int causeCount = 0;
        for (ValidationException exception : causes) {
            causeCount += exception.getViolationCount();
        }
        return Math.max(1, causeCount);
    }

    private static List getAllMessages(final List causes) {
        List messages = Lists.newArrayList();

        for (ValidationException cause : causes) {
            if (cause.getCausingExceptions().isEmpty()) {
                messages.add(cause.getMessage());
            } else {
                messages.addAll(getAllMessages(cause.getCausingExceptions()));
            }
        }

        return messages;
    }

    /**
     * Sort of static factory method. It is used by {@link ObjectSchema} and {@link ArraySchema} to
     * create {@code ValidationException}s, handling the case of multiple violations occuring during
     * validation.
     * 

*

    *
  • If {@code failures} is empty, then it doesn't do anything
  • *
  • If {@code failures} contains 1 exception instance, then that will be thrown
  • *
  • Otherwise a new exception instance will be created, its {@link #getViolatedSchema() * violated schema} will be {@code rootFailingSchema}, and its {@link #getCausingExceptions() * causing exceptions} will be the {@code failures} list
  • *
* * @param rootFailingSchema the schema which detected the {@code failures} * @param failures list containing validation failures to be thrown by this method */ public static void throwFor(final Schema rootFailingSchema, final List failures) { int failureCount = failures.size(); if (failureCount == 0) { return; } else if (failureCount == 1) { throw failures.get(0); } else { throw new ValidationException(rootFailingSchema, new ArrayList<>(failures)); } } private final StringBuilder pointerToViolation; private final transient Schema violatedSchema; private final List causingExceptions; private final String keyword; /** * Deprecated, use {@code ValidationException(Schema, Class, Object)} instead. * * @param expectedType the expected type * @param actualValue the violating value */ @Deprecated public ValidationException(final Class expectedType, final Object actualValue) { this(null, expectedType, actualValue); } /** * Constructor, creates an instance with {@code keyword="type"}. * * @param violatedSchema the schema instance which detected the schema violation * @param expectedType the expected type * @param actualValue the violating value */ public ValidationException(final Schema violatedSchema, final Class expectedType, final Object actualValue) { this(violatedSchema, expectedType, actualValue, "type"); } /** * Constructor for type-mismatch failures. It is usually more convenient to use * {@link #ValidationException(Schema, Class, Object)} instead. * * @param violatedSchema the schema instance which detected the schema violation * @param expectedType the expected type * @param actualValue the violating value * @param keyword the violating keyword */ public ValidationException(final Schema violatedSchema, final Class expectedType, final Object actualValue, final String keyword) { this(violatedSchema, new StringBuilder("#"), "expected type: " + expectedType.getSimpleName() + ", found: " + (actualValue == null ? "null" : actualValue.getClass().getSimpleName()), Collections.emptyList(), keyword); } private ValidationException(final Schema rootFailingSchema, final List causingExceptions) { this(rootFailingSchema, new StringBuilder("#"), getViolationCount(causingExceptions) + " schema violations found", causingExceptions); } /** * Constructor. * * @param violatedSchema the schema instance which detected the schema violation * @param message the readable exception message * @deprecated use one of the constructors which explicitly specify the violated keyword instead */ @Deprecated public ValidationException(final Schema violatedSchema, final String message) { this(violatedSchema, new StringBuilder("#"), message, Collections.emptyList()); } /** * Constructor. * * @param violatedSchema the schama instance which detected the schema violation * @param message the readable exception message * @param keyword the violated keyword */ public ValidationException(final Schema violatedSchema, final String message, final String keyword) { this(violatedSchema, new StringBuilder("#"), message, Collections.emptyList(), keyword); } /*** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param pointerToViolation * a JSON pointer denoting the part of the document which violates the schema * @param message * the readable exception message * @param causingExceptions * a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @deprecated please explicitly specify the violated keyword using one of these constructors: *
    *
  • {@link #ValidationException(Schema, StringBuilder, String, List, String)} *
  • {@link #ValidationException(Schema, String, String)} *
  • {@link #ValidationException(Schema, Class, Object, String)} *
*/ @Deprecated ValidationException(final Schema violatedSchema, final StringBuilder pointerToViolation, final String message, final List causingExceptions) { this(violatedSchema, pointerToViolation, message, causingExceptions, null); } /*** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param pointerToViolation * a JSON pointer denoting the part of the document which violates the schema * @param message * the readable exception message * @param causingExceptions * a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @param keyword * the violated keyword */ ValidationException(final Schema violatedSchema, final StringBuilder pointerToViolation, final String message, final List causingExceptions, final String keyword) { super(message); this.violatedSchema = violatedSchema; this.pointerToViolation = pointerToViolation; this.causingExceptions = Collections.unmodifiableList(causingExceptions); this.keyword = keyword; } /** * Deprecated, use {@code ValidationException(Schema, String)} instead. * * @param message readable exception message */ @Deprecated public ValidationException(final String message) { this((Schema) null, new StringBuilder("#"), message, Collections.emptyList()); } private ValidationException(final StringBuilder pointerToViolation, final Schema violatedSchema, final String message, final List causingExceptions, final String keyword) { this(violatedSchema, pointerToViolation, message, causingExceptions, keyword); } /** * Constructor. * * @param violatedSchema the schema instance which detected the schema violation * @param message the readable exception message * @param causingExceptions a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @deprecated use one of the constructors which explicitly specify the keyword instead */ @Deprecated public ValidationException(final Schema violatedSchema, final String message, final List causingExceptions) { this(violatedSchema, new StringBuilder("#"), message, causingExceptions); } private String escapeFragment(final String fragment) { return fragment.replace("~", "~0").replace("/", "~1"); } public List getCausingExceptions() { return causingExceptions; } /** * Returns all messages collected from all violations, including nested causing exceptions. * * @return all messages */ public List getAllMessages() { if (causingExceptions.isEmpty()) { return Collections.singletonList(getMessage()); } else { return getAllMessages(causingExceptions); } } /** * Returns a programmer-readable error description prepended by {@link #getPointerToViolation() * the pointer to the violating fragment} of the JSON document. * * @return the error description */ @Override public String getMessage() { return getPointerToViolation() + ": " + super.getMessage(); } /** * Returns a programmer-readable error description. Unlike {@link #getMessage()} this doesn't * contain the JSON pointer denoting the violating document fragment. * * @return the error description */ public String getErrorMessage() { return super.getMessage(); } /** * A JSON pointer denoting the part of the document which violates the schema. It always points * from the root of the document to the violating data fragment, therefore it always starts with * #. * * @return the JSON pointer */ public String getPointerToViolation() { if (pointerToViolation == null) { return null; } return pointerToViolation.toString(); } public Schema getViolatedSchema() { return violatedSchema; } /** * Creates a new {@code ViolationException} instance based on this one, but with changed * {@link #getPointerToViolation() JSON pointer}. * * @param fragment the fragment of the JSON pointer to be prepended to existing pointers * @return the new instance */ public ValidationException prepend(final String fragment) { return prepend(fragment, this.violatedSchema); } /** * Creates a new {@code ViolationException} instance based on this one, but with changed * {@link #getPointerToViolation() JSON pointer} and {link {@link #getViolatedSchema() violated * schema}. * * @param fragment the fragment of the JSON pointer to be prepended to existing pointers * @param violatedSchema the violated schema, which may not be the same as {@link #getViolatedSchema()} * @return the new {@code ViolationException} instance */ public ValidationException prepend(final String fragment, final Schema violatedSchema) { final String escapedFragment = escapeFragment(requireNonNull(fragment, "fragment cannot be null")); StringBuilder newPointer = this.pointerToViolation.insert(1, '/').insert(2, escapedFragment); List prependedCausingExceptions = FluentIterable.from(causingExceptions) .transform(new Function() { @Override public ValidationException apply(ValidationException exc) { return exc.prepend(escapedFragment); } }) .toList(); return new ValidationException(newPointer, violatedSchema, super.getMessage(), prependedCausingExceptions, this.keyword); } public int getViolationCount() { return getViolationCount(causingExceptions); } public String getKeyword() { return keyword; } /** * Creates a JSON representation of the failure. *

* The returned {@code JSONObject} contains the following keys: *

    *
  • {@code "message"}: a programmer-friendly exception message. This value is a non-nullable * string.
  • *
  • {@code "keyword"}: a JSON Schema keyword which was used in the schema and violated by the * input JSON. This value is a nullable string.
  • *
  • {@code "pointerToViolation"}: a JSON Pointer denoting the path from the root of the * document to the invalid fragment of it. This value is a non-nullable string. See * {@link #getPointerToViolation()}
  • *
  • {@code "causingExceptions"}: is a (possibly empty) array of violations which caused this * exceptions. See {@link #getCausingExceptions()}
  • *
* * @return a JSON description of the validation error */ public JSONObject toJSON() { JSONObject rval = new JSONObject(); rval.put("keyword", keyword); if (pointerToViolation == null) { rval.put("pointerToViolation", JSONObject.NULL); } else { rval.put("pointerToViolation", getPointerToViolation()); } rval.put("message", super.getMessage()); List causeJsons = FluentIterable.from(causingExceptions) .transform(new Function() { @Override public JSONObject apply(ValidationException input) { return input.toJSON(); } }) .toList(); rval.put("causingExceptions", new JSONArray(causeJsons)); return rval; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy