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.adapter;
018
019import org.apache.juneau.bean.rfc7807.*;
020import org.apache.juneau.http.response.*;
021
022/**
023 * Static adapters that convert Juneau HTTP error types into
024 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807">RFC 7807</a> {@link Problem} beans.
025 *
026 * <p>
027 * This adapter lives in {@code juneau-rest-common} (rather than the {@code juneau-bean-rfc7807} bean module) so the
028 * bean module stays free of any {@code juneau-rest-common} dependency. The package is co-located with the bean's
029 * package namespace so the cross-reference is discoverable from {@link Problem}'s javadoc.
030 *
031 * <h5 class='section'>Example:</h5>
032 * <p class='bjava'>
033 *    <jk>try</jk> {
034 *       ...
035 *    } <jk>catch</jk> (NotFound <jv>e</jv>) {
036 *       Problem <jv>p</jv> = ProblemAdapters.<jsm>fromException</jsm>(<jv>e</jv>);
037 *       <jc>// p.getStatus() == 404, p.getTitle() == "Not Found", p.getDetail() == e.getMessage()</jc>
038 *    }
039 * </p>
040 *
041 * <h5 class='section'>Notes:</h5>
042 * <ul class='spaced-list'>
043 *    <li>{@code type} is intentionally left {@code null}. RFC 7807 &sect;3.1 defines {@code about:blank} as the default
044 *       when the field is absent; the bean serializes a {@code null} {@code type} as an omitted field rather than as
045 *       {@code "type":"about:blank"} on the wire (preserves the absent-vs-explicit distinction).
046 *    <li>{@code detail} is suppressed when the exception's message exactly matches the reason phrase &mdash; the bare
047 *       <c><jk>new</jk> NotFound()</c> case where the message would just echo {@code "Not Found"}.
048 * </ul>
049 *
050 * <h5 class='section'>See Also:</h5><ul>
051 *    <li class='jc'>{@link Problem}
052 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanRfc7807">juneau-bean-rfc7807</a>
053 * </ul>
054 */
055public final class ProblemAdapters {
056
057   private ProblemAdapters() {}
058
059   /**
060    * Builds a {@link Problem} from a {@link BasicHttpException} (or any subclass like {@code NotFound},
061    * {@code InternalServerError}, etc).
062    *
063    * <p>
064    * Mapping:
065    * <ul class='spaced-list'>
066    *    <li>{@code status} &larr; {@link BasicHttpException#getStatusCode()}
067    *    <li>{@code title} &larr; {@link org.apache.juneau.http.HttpStatusLine#getReasonPhrase()} (may be {@code null})
068    *    <li>{@code detail} &larr; {@link BasicHttpException#getMessage()}, but only when it differs from the reason
069    *       phrase; otherwise {@code null} so the bare <c><jk>new</jk> NotFound()</c> case omits a redundant
070    *       {@code "detail":"Not Found"} field.
071    *    <li>{@code type}, {@code instance} &larr; never set by this adapter (callers add them via the fluent setters
072    *       on the returned bean if a {@code type} URI is appropriate).
073    * </ul>
074    *
075    * @param e The exception to adapt. May be <jk>null</jk>.
076    * @return A new {@link Problem} populated from the exception, or <jk>null</jk> if {@code e} is <jk>null</jk>.
077    */
078   public static Problem fromException(BasicHttpException e) {
079      if (e == null)
080         return null;
081      var statusLine = e.getStatusLine();
082      var reasonPhrase = statusLine == null ? null : statusLine.getReasonPhrase(); // HTT — statusLine is always set in BasicHttpException constructors
083      var message = e.getMessage();
084      var detail = (message == null || message.equals(reasonPhrase)) ? null : message; // HTT — getMessage() always returns non-null (falls back to reasonPhrase)
085      return new Problem()
086         .setStatus(e.getStatusCode())
087         .setTitle(reasonPhrase)
088         .setDetail(detail);
089   }
090}