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
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.net.*;
022import java.util.*;
023
024import org.apache.juneau.commons.bean.*;
025import org.apache.juneau.marshall.*;
026
027/**
028 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807">RFC 7807 &mdash; Problem Details for HTTP APIs</a>
029 * bean.
030 *
031 * <p>
032 * Represents the canonical {@code application/problem+json} document. The five standard members defined by
033 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">&sect;3.1</a> are modelled as typed
034 * properties; arbitrary <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.2">&sect;3.2</a>
035 * extension members flatten into the top-level JSON object via a {@link BeanProp @BeanProp("*")} triplet.
036 *
037 * <h5 class='section'>Example:</h5>
038 * <p class='bjava'>
039 *    Problem <jv>problem</jv> = <jk>new</jk> Problem()
040 *       .setType(<jk>new</jk> URI(<js>"https://example.com/probs/out-of-credit"</js>))
041 *       .setTitle(<js>"You do not have enough credit."</js>)
042 *       .setStatus(403)
043 *       .setDetail(<js>"Your current balance is 30, but that costs 50."</js>)
044 *       .setInstance(<jk>new</jk> URI(<js>"/account/12345/msgs/abc"</js>))
045 *       .set(<js>"balance"</js>, 30)
046 *       .set(<js>"accounts"</js>, <jsm>list</jsm>(<js>"/account/12345"</js>, <js>"/account/67890"</js>));
047 *
048 *    String <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.write(<jv>problem</jv>);
049 * </p>
050 *
051 * <h5 class='section'>Notes:</h5>
052 * <ul class='spaced-list'>
053 *    <li>The {@code type} field is nullable. {@link #getType()} returns the raw value (possibly {@code null});
054 *       {@link #getTypeOrDefault()} returns the spec default {@code "about:blank"} per
055 *       <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">RFC 7807 &sect;3.1</a> when the
056 *       field is unset. The field is never eagerly defaulted, so a fresh {@code Problem} round-trips without a
057 *       synthetic {@code "type":"about:blank"} on the wire.
058 *    <li>RFC 7807 was obsoleted by <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9457">RFC 9457</a> in
059 *       July 2023, but the data model and the {@code application/problem+json} IANA registration are unchanged.
060 * </ul>
061 *
062 * <h5 class='section'>See Also:</h5><ul>
063 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanRfc7807">juneau-bean-rfc7807</a>
064 * </ul>
065 */
066@Marshalled
067public class Problem {
068
069   /**
070    * The default {@code type} URI defined by
071    * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">RFC 7807 &sect;3.1</a> for problem
072    * documents that omit the {@code type} member.
073    */
074   public static final URI DEFAULT_TYPE = URI.create("about:blank");
075
076   private URI type;
077   private String title;
078   private Integer status;
079   private String detail;
080   private URI instance;
081   private Map<String,Object> extra;
082
083   /**
084    * Convenience factory for the common &quot;build a problem from a status code + title + detail&quot; call site.
085    *
086    * <p>
087    * Populates only the {@code status}, {@code title}, and {@code detail} members. Callers wire from
088    * {@link Throwable}s or HTTP exception types themselves &mdash; for example:
089    * <p class='bjava'>
090    *    Problem.<jsm>fromStatus</jsm>(<jv>e</jv>.getStatusCode(), <jv>e</jv>.getStatusLine().getReasonPhrase(), <jv>e</jv>.getMessage());
091    * </p>
092    *
093    * @param status The HTTP status code (e.g. {@code 404}).
094    * @param title A short, human-readable summary of the problem type. Can be <jk>null</jk> to leave the title unset.
095    * @param detail A human-readable explanation specific to this occurrence. Can be <jk>null</jk> to leave the detail unset.
096    * @return A new {@link Problem} populated with the supplied values.
097    */
098   public static Problem fromStatus(int status, String title, String detail) {
099      return new Problem().setStatus(status).setTitle(title).setDetail(detail);
100   }
101
102   /**
103    * The <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">type</a> URI that identifies
104    * the problem type.
105    *
106    * <p>
107    * Returns the raw field value, which may be <jk>null</jk> when the {@code type} member is absent. Use
108    * {@link #getTypeOrDefault()} to get the spec default {@code "about:blank"} for absent values.
109    *
110    * @return The {@code type} URI, or <jk>null</jk> if not set.
111    */
112   public URI getType() {
113      return type;
114   }
115
116   /**
117    * Returns the {@code type} URI, defaulting to {@link #DEFAULT_TYPE} ({@code "about:blank"}) per
118    * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">RFC 7807 &sect;3.1</a> when the
119    * field is <jk>null</jk>.
120    *
121    * <p>
122    * The underlying field is left untouched, so this method never causes a synthetic {@code "type":"about:blank"}
123    * to appear on the wire.
124    *
125    * @return The {@code type} URI, never <jk>null</jk>.
126    */
127   @BeanIgnore
128   public URI getTypeOrDefault() {
129      return or(type, DEFAULT_TYPE);
130   }
131
132   /**
133    * Sets the {@code type} URI.
134    *
135    * @param value The new value. Can be <jk>null</jk> to unset the property.
136    * @return This object.
137    */
138   public Problem setType(URI value) {
139      type = value;
140      return this;
141   }
142
143   /**
144    * The <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">title</a> &mdash; a short,
145    * human-readable summary of the problem type.
146    *
147    * @return The title, or <jk>null</jk> if not set.
148    */
149   public String getTitle() {
150      return title;
151   }
152
153   /**
154    * Sets the {@code title}.
155    *
156    * @param value The new value. Can be <jk>null</jk> to unset the property.
157    * @return This object.
158    */
159   public Problem setTitle(String value) {
160      title = value;
161      return this;
162   }
163
164   /**
165    * The <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">status</a> &mdash; the HTTP
166    * status code originally generated by the origin server for this occurrence.
167    *
168    * <p>
169    * Modelled as {@link Integer} (boxed) because RFC 7807 declares {@code status} as OPTIONAL.
170    *
171    * @return The status code, or <jk>null</jk> if not set.
172    */
173   public Integer getStatus() {
174      return status;
175   }
176
177   /**
178    * Sets the {@code status} HTTP status code.
179    *
180    * @param value The new value. Can be <jk>null</jk> to unset the property.
181    * @return This object.
182    */
183   public Problem setStatus(Integer value) {
184      status = value;
185      return this;
186   }
187
188   /**
189    * The <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">detail</a> &mdash; a
190    * human-readable explanation specific to this occurrence of the problem.
191    *
192    * @return The detail, or <jk>null</jk> if not set.
193    */
194   public String getDetail() {
195      return detail;
196   }
197
198   /**
199    * Sets the {@code detail}.
200    *
201    * @param value The new value. Can be <jk>null</jk> to unset the property.
202    * @return This object.
203    */
204   public Problem setDetail(String value) {
205      detail = value;
206      return this;
207   }
208
209   /**
210    * The <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.1">instance</a> URI that
211    * identifies the specific occurrence of the problem.
212    *
213    * @return The {@code instance} URI, or <jk>null</jk> if not set.
214    */
215   public URI getInstance() {
216      return instance;
217   }
218
219   /**
220    * Sets the {@code instance} URI.
221    *
222    * @param value The new value. Can be <jk>null</jk> to unset the property.
223    * @return This object.
224    */
225   public Problem setInstance(URI value) {
226      instance = value;
227      return this;
228   }
229
230   /**
231    * Generic <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.2">extension-member</a>
232    * keyset.
233    *
234    * @return All extension keys on this problem. Never <jk>null</jk>.
235    */
236   @BeanProp("*")
237   public Set<String> extraKeys() {
238      return extra == null ? Collections.emptySet() : u(extra.keySet());
239   }
240
241   /**
242    * Generic extension-member getter.
243    *
244    * <p>
245    * Used by the bean framework to read arbitrary
246    * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.2">&sect;3.2</a> extension members
247    * that flatten into the top-level JSON object.
248    *
249    * @param property The property name to retrieve. Can be <jk>null</jk> (a <jk>null</jk> key simply resolves to no value).
250    * @return The property value, or <jk>null</jk> if the property does not exist.
251    */
252   @BeanProp("*")
253   public Object get(String property) {
254      return extra == null ? null : extra.get(property);
255   }
256
257   /**
258    * Generic extension-member setter.
259    *
260    * <p>
261    * Used by the bean framework to set arbitrary
262    * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807#section-3.2">&sect;3.2</a> extension members.
263    * The extension map is lazily initialised on first use.
264    *
265    * @param property The property name to set. Can be <jk>null</jk> ({@link LinkedHashMap} tolerates a <jk>null</jk> key).
266    * @param value The new value for the property. Can be <jk>null</jk> (stored as <jk>null</jk>).
267    * @return This object.
268    */
269   @BeanProp("*")
270   public Problem set(String property, Object value) {
271      if (extra == null)
272         extra = m();
273      extra.put(property, value);
274      return this;
275   }
276}