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