001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.validation;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023import org.apache.juneau.http.response.*;
024
025/**
026 * Specialization of {@link BadRequest} thrown by {@link BeanValidator} when one or more Jakarta Bean Validation
027 * constraints on a {@code @Content}, {@code @FormData}, or {@code @Request}-bound parameter are violated.
028 *
029 * <p>
030 * Behaves like a plain {@code 400 Bad Request} from the wire's perspective &mdash; same status code, same reason
031 * phrase, same default JSON envelope shape used by the rest of the framework's exception path &mdash; but adds a
032 * structured, serialized {@link #getViolations() violations list} so the response body can include the list of
033 * field-level errors that produced the failure.
034 *
035 * <h5 class='section'>Wire shape:</h5>
036 * <p>
037 * The response body depends on whether RFC 7807 problem-details mode is enabled on the resource:
038 * <ul class='spaced-list'>
039 *    <li><b>Problem-details ON</b> &mdash; {@code application/problem+json} with the standard {@code status} /
040 *       {@code title} / {@code detail} members plus an {@code errors[]} extension array populated from
041 *       {@link #getViolations()}.
042 *    <li><b>Problem-details OFF (default)</b> &mdash; {@code application/json} with a simple
043 *       {@code { "status":400, "errors":[ ... ] }} envelope.
044 * </ul>
045 *
046 * <h5 class='section'>See Also:</h5><ul>
047 *    <li class='jc'>{@link ValidationViolation}
048 *    <li class='jc'>{@link BeanValidator}
049 *    <li class='jc'>{@link BadRequest}
050 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerValidation">Jakarta Bean Validation</a>
051 * </ul>
052 *
053 * @since 10.0.0
054 */
055@SuppressWarnings({
056   "java:S110", // Deep inheritance inherent to the exception hierarchy
057   "java:S2160" // Exceptions are compared by identity, not by their violations payload; no equals() override needed
058})
059public class ValidationException extends BadRequest {
060
061   private static final long serialVersionUID = 1L;
062
063   /** The default detail message used when constructed without an explicit message. */
064   public static final String DEFAULT_MESSAGE = "Request validation failed";
065
066   private final transient List<ValidationViolation> violations;
067
068   /**
069    * Constructor.
070    *
071    * @param violations The list of constraint violations. Must not be <jk>null</jk> (use an empty list to signal
072    *    &quot;validation requested but no violations&quot; &mdash; though that case never actually throws in practice).
073    */
074   public ValidationException(List<ValidationViolation> violations) {
075      super(DEFAULT_MESSAGE);
076      this.violations = List.copyOf(violations);
077   }
078
079   /**
080    * Constructor with a custom detail message.
081    *
082    * @param violations The list of constraint violations. Must not be <jk>null</jk>.
083    * @param msg The detail message. May be <jk>null</jk>.
084    *    Treated as a format pattern when {@code args} is non-empty.
085    * @param args Optional message arguments.
086    */
087   public ValidationException(List<ValidationViolation> violations, String msg, Object...args) {
088      super(msg, args);
089      this.violations = List.copyOf(violations);
090   }
091
092   /**
093    * Returns the immutable list of constraint violations that produced this exception.
094    *
095    * @return The list of violations, never <jk>null</jk> and never modifiable.
096    */
097   public List<ValidationViolation> getViolations() {
098      return violations;
099   }
100
101   /**
102    * Returns a defensive, mutable copy of the violation list for callers that need to inspect / filter it
103    * without mutating the exception's own state.
104    *
105    * @return A new mutable copy. Never <jk>null</jk>.
106    */
107   public List<ValidationViolation> copyViolations() {
108      return cp(violations);
109   }
110}