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.bean.rfc7807;
018
019/**
020 * SPI for mapping a domain or framework {@link Throwable} into an
021 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807">RFC 7807 / 9457</a> {@link Problem} bean.
022 *
023 * <p>
024 * Implementations contribute domain-specific error translation without subclassing the request-processor chain.
025 * A {@code ProblemMapper} bean is registered via the resource bean store (e.g. a {@code @Bean ProblemMapper foo()}
026 * factory method on a {@code @Rest}-annotated resource, or any other mechanism that contributes named beans to
027 * the resource's bean store); the server-side processor picks it up automatically when an opted-in operation
028 * throws or returns a matching exception.
029 *
030 * <h5 class='section'>Discovery model</h5>
031 * <p>
032 * The processor collects all {@code ProblemMapper} beans visible from the resource's bean store, then picks the
033 * <b>most-specific</b> mapper for the exception class hierarchy (a mapper whose {@link #getExceptionType()} is the
034 * closest superclass of the thrown exception wins; ties are broken by registration order, with the bean-store
035 * walk order serving as the tiebreaker). Mappers that return {@code null} are skipped and the next-most-specific
036 * mapper is tried, finally falling back to the default {@code ProblemAdapters.fromException(...)} adaptation in
037 * {@code juneau-rest-common} for {@code BasicHttpException} subclasses.
038 *
039 * <h5 class='section'>Example:</h5>
040 * <p class='bjava'>
041 *    <jk>public class</jk> MyDomainProblemMapper <jk>implements</jk> ProblemMapper&lt;MyDomainException&gt; {
042 *
043 *       <ja>@Override</ja>
044 *       <jk>public</jk> Class&lt;MyDomainException&gt; getExceptionType() {
045 *          <jk>return</jk> MyDomainException.<jk>class</jk>;
046 *       }
047 *
048 *       <ja>@Override</ja>
049 *       <jk>public</jk> Problem map(MyDomainException <jv>e</jv>) {
050 *          <jk>return</jk> Problem.<jsm>fromStatus</jsm>(422, <js>"Domain rule violated"</js>, <jv>e</jv>.getMessage())
051 *             .setType(<jk>new</jk> URI(<js>"https://example.com/probs/domain"</js>))
052 *             .set(<js>"rule"</js>, <jv>e</jv>.getRuleId());
053 *       }
054 *    }
055 *
056 *    <ja>@Rest</ja>(problemDetails=<js>"true"</js>)
057 *    <jk>public class</jk> MyResource {
058 *
059 *       <jc>// Bean-store registration: factory method on the resource class.</jc>
060 *       <ja>@Bean</ja> <jk>public</jk> ProblemMapper&lt;MyDomainException&gt; myMapper() {
061 *          <jk>return new</jk> MyDomainProblemMapper();
062 *       }
063 *
064 *       <ja>@RestGet</ja>(<js>"/buy"</js>)
065 *       <jk>public</jk> Receipt buy() {
066 *          <jk>throw new</jk> MyDomainException(...);  <jc>// translated by myMapper()</jc>
067 *       }
068 *    }
069 * </p>
070 *
071 * <h5 class='section'>See Also:</h5><ul>
072 *    <li class='jc'>{@link Problem}
073 *    <li class='jc'>{@link ProblemException}
074 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerProblemDetails">RFC 7807 / 9457 Problem Details</a>
075 * </ul>
076 *
077 * @param <T> The {@link Throwable} subtype this mapper handles.
078 */
079public interface ProblemMapper<T extends Throwable> {
080
081   /**
082    * Returns the exception type this mapper handles.
083    *
084    * <p>
085    * The processor matches mappers by checking
086    * {@code getExceptionType().isAssignableFrom(thrown.getClass())} for each registered mapper, then picks the
087    * mapper whose {@code getExceptionType()} sits closest to the thrown class in the hierarchy. Implementations
088    * usually return the same class their {@code <T>} parameter binds; declaring it explicitly here avoids
089    * reflective inspection of the generic type at runtime.
090    *
091    * @return The exception class this mapper handles. Must not be <jk>null</jk>.
092    */
093   Class<T> getExceptionType();
094
095   /**
096    * Maps the supplied exception to a {@link Problem} bean.
097    *
098    * <p>
099    * Returning <jk>null</jk> signals "no opinion on this exception"; the processor then tries the next-most-specific
100    * registered mapper (and, ultimately, the built-in default fallback). Returning a populated {@link Problem}
101    * causes the processor to emit that bean as {@code application/problem+json}.
102    *
103    * @param exception The exception to translate. Never <jk>null</jk>.
104    * @return The {@link Problem} bean to emit, or <jk>null</jk> to defer to the next mapper.
105    */
106   Problem map(T exception);
107}