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.openapi3;
018
019import static org.apache.juneau.common.utils.Utils.*;
020import static org.apache.juneau.internal.CollectionUtils.*;
021import static org.apache.juneau.internal.ConverterUtils.*;
022
023import java.net.*;
024import java.util.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.common.utils.*;
028import org.apache.juneau.internal.*;
029
030/**
031 * Describes a single response from an API operation.
032 *
033 * <p>
034 * The Response Object describes a single response from an API operation, including a description, headers, content, and links.
035 * Responses are returned based on the HTTP status code, with the most common being success responses (2xx), redirects (3xx),
036 * client errors (4xx), and server errors (5xx).
037 *
038 * <h5 class='section'>OpenAPI Specification:</h5>
039 * <p>
040 * The Response Object is composed of the following fields:
041 * <ul class='spaced-list'>
042 *    <li><c>description</c> (string, REQUIRED) - A short description of the response (CommonMark syntax may be used)
043 *    <li><c>headers</c> (map of {@link HeaderInfo}) - Maps a header name to its definition
044 *    <li><c>content</c> (map of {@link MediaType}) - A map containing descriptions of potential response payloads (keys are media types)
045 *    <li><c>links</c> (map of {@link Link}) - A map of operations links that can be followed from the response
046 * </ul>
047 *
048 * <h5 class='section'>Example:</h5>
049 * <p class='bjava'>
050 *    <jc>// Create a successful response with JSON content</jc>
051 *    Response <jv>response</jv> = <jk>new</jk> Response()
052 *       .setDescription(<js>"A list of pets"</js>)
053 *       .setContent(
054 *          JsonMap.<jsm>of</jsm>(
055 *             <js>"application/json"</js>, <jk>new</jk> MediaType()
056 *                .setSchema(
057 *                   <jk>new</jk> SchemaInfo()
058 *                      .setType(<js>"array"</js>)
059 *                      .setItems(<jk>new</jk> Items().setRef(<js>"#/components/schemas/Pet"</js>))
060 *                )
061 *          )
062 *       )
063 *       .setHeaders(
064 *          JsonMap.<jsm>of</jsm>(
065 *             <js>"X-Rate-Limit"</js>, <jk>new</jk> HeaderInfo()
066 *                .setDescription(<js>"Requests per hour allowed by the user"</js>)
067 *                .setSchema(<jk>new</jk> SchemaInfo().setType(<js>"integer"</js>))
068 *          )
069 *       );
070 * </p>
071 *
072 * <h5 class='section'>See Also:</h5><ul>
073 *    <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#response-object">OpenAPI Specification &gt; Response Object</a>
074 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-responses/">OpenAPI Describing Responses</a>
075 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
076 * </ul>
077 */
078public class Response extends OpenApiElement{
079
080   private String description;
081   private Map<String,HeaderInfo> headers;
082   private Map<String,MediaType> content;
083   private Map<String,Link> links;
084
085   /**
086    * Default constructor.
087    */
088   public Response() { }
089
090   /**
091    * Copy constructor.
092    *
093    * @param copyFrom The object to copy.
094    */
095   public Response(Response copyFrom) {
096      super(copyFrom);
097
098      this.description = copyFrom.description;
099      this.headers = copyOf(copyFrom.headers, HeaderInfo::copy);
100      this.content = copyOf(copyFrom.content, MediaType::copy);
101      this.links = copyOf(copyFrom.links, Link::copy);
102   }
103
104   /**
105    * Make a deep copy of this object.
106    *
107    * @return A deep copy of this object.
108    */
109   public Response copy() {
110      return new Response(this);
111   }
112
113   @Override /* Overridden from OpenApiElement */
114   protected Response strict() {
115      super.strict();
116      return this;
117   }
118
119   @Override /* Overridden from OpenApiElement */
120   public Response strict(Object value) {
121      super.strict(value);
122      return this;
123   }
124
125   /**
126    * Bean property getter:  <property>Description</property>.
127    *
128    * <p>
129    * The URL pointing to the contact information.
130    *
131    * @return The property value, or <jk>null</jk> if it is not set.
132    */
133   public String getDescription() {
134      return description;
135   }
136
137   /**
138    * Bean property setter:  <property>Description</property>.
139    *
140    * <p>
141    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
142    * <br>Strings must be valid URIs.
143    *
144    * <p>
145    * URIs defined by {@link UriResolver} can be used for values.
146    *
147    * @param value
148    *    The new value for this property.
149    *    <br>Can be <jk>null</jk> to unset the property.
150    * @return This object
151    */
152   public Response setDescription(String value) {
153      description = value;
154      return this;
155   }
156
157   /**
158    * Bean property getter:  <property>headers</property>.
159    *
160    * @return The property value, or <jk>null</jk> if it is not set.
161    */
162   public Map<String, HeaderInfo> getHeaders() {
163      return headers;
164   }
165
166   /**
167    * Returns the header with the specified name.
168    *
169    * @param name The header name.  Must not be <jk>null</jk>.
170    * @return The header info, or <jk>null</jk> if not found.
171    */
172   public HeaderInfo getHeader(String name) {
173      assertArgNotNull("name", name);
174      return headers == null ? null : headers.get(name);
175   }
176
177   /**
178    * Bean property setter:  <property>headers</property>.
179    *
180    * @param value
181    *    The new value for this property.
182    *    <br>Can be <jk>null</jk> to unset the property.
183    * @return This object
184    */
185   public Response setHeaders(Map<String, HeaderInfo> value) {
186      headers = copyOf(value);
187      return this;
188   }
189
190   /**
191    * Adds one or more values to the <property>headers</property> property.
192    *
193    * @param key The mapping key.  Must not be <jk>null</jk>.
194    * @param value The values to add to this property.  Must not be <jk>null</jk>.
195    * @return This object
196    */
197   public Response addHeader(String key, HeaderInfo value) {
198      assertArgNotNull("key", key);
199      assertArgNotNull("value", value);
200      headers = mapBuilder(headers).sparse().add(key, value).build();
201      return this;
202   }
203
204   /**
205    * Bean property getter:  <property>content</property>.
206    *
207    * @return The property value, or <jk>null</jk> if it is not set.
208    */
209   public Map<String, MediaType> getContent() {
210      return content;
211   }
212
213   /**
214    * Returns the content with the specified media type.
215    *
216    * @param mediaType The media type.  Must not be <jk>null</jk>.
217    * @return The media type info, or <jk>null</jk> if not found.
218    */
219   public MediaType getContent(String mediaType) {
220      assertArgNotNull("mediaType", mediaType);
221      return content == null ? null : content.get(mediaType);
222   }
223
224   /**
225    * Bean property setter:  <property>content</property>.
226    *
227    * @param value
228    *    The new value for this property.
229    *    <br>Can be <jk>null</jk> to unset the property.
230    * @return This object
231    */
232   public Response setContent(Map<String, MediaType> value) {
233      content = copyOf(value);
234      return this;
235   }
236
237   /**
238    * Adds one or more values to the <property>content</property> property.
239    *
240    * @param key The mapping key.  Must not be <jk>null</jk>.
241    * @param value The values to add to this property.  Must not be <jk>null</jk>.
242    * @return This object
243    */
244   public Response addContent(String key, MediaType value) {
245      assertArgNotNull("key", key);
246      assertArgNotNull("value", value);
247      content = mapBuilder(content).sparse().add(key, value).build();
248      return this;
249   }
250
251   /**
252    * Bean property getter:  <property>links</property>.
253    *
254    * @return The property value, or <jk>null</jk> if it is not set.
255    */
256   public Map<String, Link> getLinks() {
257      return links;
258   }
259
260   /**
261    * Returns the link with the specified name.
262    *
263    * @param name The link name.  Must not be <jk>null</jk>.
264    * @return The link info, or <jk>null</jk> if not found.
265    */
266   public Link getLink(String name) {
267      assertArgNotNull("name", name);
268      return links == null ? null : links.get(name);
269   }
270
271   /**
272    * Bean property setter:  <property>links</property>.
273    *
274    * @param value
275    *    The new value for this property.
276    *    <br>Can be <jk>null</jk> to unset the property.
277    * @return This object
278    */
279   public Response setLinks(Map<String, Link> value) {
280      links = copyOf(value);
281      return this;
282   }
283
284   /**
285    * Adds one or more values to the <property>links</property> property.
286    *
287    * @param key The mapping key.  Must not be <jk>null</jk>.
288    * @param value The values to add to this property.  Must not be <jk>null</jk>.
289    * @return This object
290    */
291   public Response addLink(String key, Link value) {
292      assertArgNotNull("key", key);
293      assertArgNotNull("value", value);
294      links = mapBuilder(links).sparse().add(key, value).build();
295      return this;
296   }
297
298   @Override /* Overridden from OpenApiElement */
299   public <T> T get(String property, Class<T> type) {
300      assertArgNotNull("property", property);
301      return switch (property) {
302         case "description" -> toType(getDescription(), type);
303         case "content" -> toType(getContent(), type);
304         case "headers" -> toType(getHeaders(), type);
305         case "links" -> toType(getLinks(), type);
306         default -> super.get(property, type);
307      };
308   }
309
310   @Override /* Overridden from OpenApiElement */
311   public Response set(String property, Object value) {
312      assertArgNotNull("property", property);
313      return switch (property) {
314         case "content" -> setContent(mapBuilder(String.class,MediaType.class).sparse().addAny(value).build());
315         case "description" -> setDescription(Utils.s(value));
316         case "headers" -> setHeaders(mapBuilder(String.class,HeaderInfo.class).sparse().addAny(value).build());
317         case "links" -> setLinks(mapBuilder(String.class,Link.class).sparse().addAny(value).build());
318         default -> {
319            super.set(property, value);
320            yield this;
321         }
322      };
323   }
324
325   @Override /* Overridden from OpenApiElement */
326   public Set<String> keySet() {
327      var s = setBuilder(String.class)
328         .addIf(content != null, "content")
329         .addIf(description != null, "description")
330         .addIf(headers != null, "headers")
331         .addIf(links != null, "links")
332         .build();
333      return new MultiSet<>(s, super.keySet());
334   }
335}