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.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022import static org.apache.juneau.internal.ConverterUtils.*;
023
024import java.net.*;
025import java.util.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.commons.collections.*;
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 = map();
082   private Map<String,MediaType> content = map();
083   private 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      if (nn(copyFrom.headers))
100         headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy));
101      if (nn(copyFrom.content))
102         content.putAll(copyOf(copyFrom.content, MediaType::copy));
103      if (nn(copyFrom.links))
104         links.putAll(copyOf(copyFrom.links, Link::copy));
105   }
106
107   /**
108    * Adds one or more values to the <property>content</property> property.
109    *
110    * @param key The mapping key.  Must not be <jk>null</jk>.
111    * @param value The values to add to this property.  Must not be <jk>null</jk>.
112    * @return This object
113    */
114   public Response addContent(String key, MediaType value) {
115      assertArgNotNull("key", key);
116      assertArgNotNull("value", value);
117      content.put(key, value);
118      return this;
119   }
120
121   /**
122    * Adds one or more values to the <property>headers</property> property.
123    *
124    * @param key The mapping key.  Must not be <jk>null</jk>.
125    * @param value The values to add to this property.  Must not be <jk>null</jk>.
126    * @return This object
127    */
128   public Response addHeader(String key, HeaderInfo value) {
129      assertArgNotNull("key", key);
130      assertArgNotNull("value", value);
131      headers.put(key, value);
132      return this;
133   }
134
135   /**
136    * Adds one or more values to the <property>links</property> property.
137    *
138    * @param key The mapping key.  Must not be <jk>null</jk>.
139    * @param value The values to add to this property.  Must not be <jk>null</jk>.
140    * @return This object
141    */
142   public Response addLink(String key, Link value) {
143      assertArgNotNull("key", key);
144      assertArgNotNull("value", value);
145      links.put(key, value);
146      return this;
147   }
148
149   /**
150    * Make a deep copy of this object.
151    *
152    * @return A deep copy of this object.
153    */
154   public Response copy() {
155      return new Response(this);
156   }
157
158   @Override /* Overridden from OpenApiElement */
159   public <T> T get(String property, Class<T> type) {
160      assertArgNotNull("property", property);
161      return switch (property) {
162         case "description" -> toType(getDescription(), type);
163         case "content" -> toType(getContent(), type);
164         case "headers" -> toType(getHeaders(), type);
165         case "links" -> toType(getLinks(), type);
166         default -> super.get(property, type);
167      };
168   }
169
170   /**
171    * Bean property getter:  <property>content</property>.
172    *
173    * @return The property value, or <jk>null</jk> if it is not set.
174    */
175   public Map<String,MediaType> getContent() { return nullIfEmpty(content); }
176
177   /**
178    * Returns the content with the specified media type.
179    *
180    * @param mediaType The media type.  Must not be <jk>null</jk>.
181    * @return The media type info, or <jk>null</jk> if not found.
182    */
183   public MediaType getContent(String mediaType) {
184      assertArgNotNull("mediaType", mediaType);
185      return content.get(mediaType);
186   }
187
188   /**
189    * Bean property getter:  <property>Description</property>.
190    *
191    * <p>
192    * The URL pointing to the contact information.
193    *
194    * @return The property value, or <jk>null</jk> if it is not set.
195    */
196   public String getDescription() { return description; }
197
198   /**
199    * Returns the header with the specified name.
200    *
201    * @param name The header name.  Must not be <jk>null</jk>.
202    * @return The header info, or <jk>null</jk> if not found.
203    */
204   public HeaderInfo getHeader(String name) {
205      assertArgNotNull("name", name);
206      return headers.get(name);
207   }
208
209   /**
210    * Bean property getter:  <property>headers</property>.
211    *
212    * @return The property value, or <jk>null</jk> if it is not set.
213    */
214   public Map<String,HeaderInfo> getHeaders() { return nullIfEmpty(headers); }
215
216   /**
217    * Returns the link with the specified name.
218    *
219    * @param name The link name.  Must not be <jk>null</jk>.
220    * @return The link info, or <jk>null</jk> if not found.
221    */
222   public Link getLink(String name) {
223      assertArgNotNull("name", name);
224      return links.get(name);
225   }
226
227   /**
228    * Bean property getter:  <property>links</property>.
229    *
230    * @return The property value, or <jk>null</jk> if it is not set.
231    */
232   public Map<String,Link> getLinks() { return nullIfEmpty(links); }
233
234   @Override /* Overridden from OpenApiElement */
235   public Set<String> keySet() {
236      // @formatter:off
237      var s = setb(String.class)
238         .addIf(ne(content), "content")
239         .addIf(nn(description), "description")
240         .addIf(ne(headers), "headers")
241         .addIf(ne(links), "links")
242         .build();
243      // @formatter:on
244      return new MultiSet<>(s, super.keySet());
245   }
246
247   @Override /* Overridden from OpenApiElement */
248   public Response set(String property, Object value) {
249      assertArgNotNull("property", property);
250      return switch (property) {
251         case "content" -> setContent(toMapBuilder(value, String.class, MediaType.class).sparse().build());
252         case "description" -> setDescription(s(value));
253         case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build());
254         case "links" -> setLinks(toMapBuilder(value, String.class, Link.class).sparse().build());
255         default -> {
256            super.set(property, value);
257            yield this;
258         }
259      };
260   }
261
262   /**
263    * Bean property setter:  <property>content</property>.
264    *
265    * @param value
266    *    The new value for this property.
267    *    <br>Can be <jk>null</jk> to unset the property.
268    * @return This object
269    */
270   public Response setContent(Map<String,MediaType> value) {
271      content.clear();
272      if (nn(value))
273         content.putAll(value);
274      return this;
275   }
276
277   /**
278    * Bean property setter:  <property>Description</property>.
279    *
280    * <p>
281    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
282    * <br>Strings must be valid URIs.
283    *
284    * <p>
285    * URIs defined by {@link UriResolver} can be used for values.
286    *
287    * @param value
288    *    The new value for this property.
289    *    <br>Can be <jk>null</jk> to unset the property.
290    * @return This object
291    */
292   public Response setDescription(String value) {
293      description = value;
294      return this;
295   }
296
297   /**
298    * Bean property setter:  <property>headers</property>.
299    *
300    * @param value
301    *    The new value for this property.
302    *    <br>Can be <jk>null</jk> to unset the property.
303    * @return This object
304    */
305   public Response setHeaders(Map<String,HeaderInfo> value) {
306      headers.clear();
307      if (nn(value))
308         headers.putAll(value);
309      return this;
310   }
311
312   /**
313    * Bean property setter:  <property>links</property>.
314    *
315    * @param value
316    *    The new value for this property.
317    *    <br>Can be <jk>null</jk> to unset the property.
318    * @return This object
319    */
320   public Response setLinks(Map<String,Link> value) {
321      links.clear();
322      if (nn(value))
323         links.putAll(value);
324      return this;
325   }
326
327   @Override /* Overridden from OpenApiElement */
328   public Response strict(Object value) {
329      super.strict(value);
330      return this;
331   }
332
333   @Override /* Overridden from OpenApiElement */
334   protected Response strict() {
335      super.strict();
336      return this;
337   }
338}