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.swagger;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.bean.swagger.SwaggerCopyUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.marshall.internal.ConverterUtils.*;
023
024import java.util.*;
025
026import org.apache.juneau.commons.collections.*;
027
028/**
029 * Describes a single response from an API operation.
030 *
031 * <p>
032 * The Response Object describes a single response from a Swagger 2.0 API operation, including a description,
033 * schema, headers, and examples. Responses are associated with HTTP status codes (e.g., 200, 404, 500).
034 *
035 * <h5 class='section'>Swagger 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
040 *    <li><c>schema</c> ({@link SchemaInfo}) - A definition of the response structure
041 *    <li><c>headers</c> (map of {@link HeaderInfo}) - Maps a header name to its definition
042 *    <li><c>examples</c> (map of any) - An example of the response message (keys are media types)
043 * </ul>
044 *
045 * <h5 class='section'>Example:</h5>
046 * <p class='bjava'>
047 *    <jc>// Construct using SwaggerBuilder.</jc>
048 *    ResponseInfo <jv>info</jv> = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>)
049 *       .schema(
050 *          <jsm>schemaInfo</jsm>
051 *             .type(<js>"array"</js>)
052 *             .items(
053 *                <jsm>items</jsm>()
054 *                   .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>)
055 *             )
056 *       );
057 *
058 *    <jc>// Serialize using JsonSerializer.</jc>
059 *    String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>info</jv>);
060 *
061 *    <jc>// Or just use toString() which does the same as above.</jc>
062 *    <jv>json</jv> = <jv>info</jv>.toString();
063 * </p>
064 * <p class='bjson'>
065 *    <jc>// Output</jc>
066 *    {
067 *       <js>"description"</js>: <js>"A complex object array response"</js>,
068 *       <js>"schema"</js>: {
069 *          <js>"type"</js>: <js>"array"</js>,
070 *          <js>"items"</js>: {
071 *             <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js>
072 *          }
073 *       }
074 *    }
075 * </p>
076 *
077 * <h5 class='section'>See Also:</h5><ul>
078 *    <li class='link'><a class="doclink" href="https://swagger.io/specification/v2/#response-object">Swagger 2.0 Specification &gt; Response Object</a>
079 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/2-0/describing-responses/">Swagger Describing Responses</a>
080 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanSwagger2">juneau-bean-swagger-v2</a>
081 * </ul>
082 */
083@SuppressWarnings({
084   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
085})
086public class ResponseInfo extends SwaggerElement {
087
088   private String description;
089   private SchemaInfo schema;
090   private Map<String,HeaderInfo> headers = map();
091   private Map<String,Object> examples = map();
092
093   /**
094    * Default constructor.
095    */
096   public ResponseInfo() {}
097
098   /**
099    * Copy constructor.
100    *
101    * @param copyFrom The object to copy.
102    */
103   public ResponseInfo(ResponseInfo copyFrom) {
104      super(copyFrom);
105
106      this.description = copyFrom.description;
107      this.schema = copyOf(copyFrom.schema);
108      examples.putAll(copyOf(copyFrom.examples));
109      headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy));
110   }
111
112   /**
113    * Bean property appender:  <property>examples</property>.
114    *
115    * <p>
116    * Adds a single value to the <property>examples</property> property.
117    *
118    * @param mimeType The mime-type string.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
119    * @param example The example.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
120    * @return This object.
121    */
122   public ResponseInfo addExample(String mimeType, Object example) {
123      reqnn("mimeType", mimeType);
124      reqnn("example", example);
125      examples.put(mimeType, example);
126      return this;
127   }
128
129   /**
130    * Bean property appender:  <property>headers</property>.
131    *
132    * @param name The header name.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
133    * @param header The header descriptions  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
134    * @return This object.
135    */
136   public ResponseInfo addHeader(String name, HeaderInfo header) {
137      reqnn("name", name);
138      reqnn("header", header);
139      headers.put(name, header);
140      return this;
141   }
142
143   /**
144    * Make a deep copy of this object.
145    *
146    * @return A deep copy of this object.
147    */
148   public ResponseInfo copy() {
149      return new ResponseInfo(this);
150   }
151
152   /**
153    * Copies any non-null fields from the specified object to this object.
154    *
155    * @param r
156    *    The object to copy fields from.
157    *    <br>Can be <jk>null</jk> (no-op).
158    * @return This object.
159    */
160   public ResponseInfo copyFrom(ResponseInfo r) {
161      if (nn(r)) {
162         if (nn(r.description))
163            description = r.description;
164         if (nn(r.schema))
165            schema = r.schema;
166         headers = r.headers;
167         examples = r.examples;
168      }
169      return this;
170   }
171
172   @Override /* Overridden from SwaggerElement */
173   public <T> T get(String property, Class<T> type) {
174      reqnn("property", property);
175      return switch (property) {
176         case "description" -> toType(getDescription(), type);
177         case "examples" -> toType(getExamples(), type);
178         case "headers" -> toType(getHeaders(), type);
179         case "schema" -> toType(getSchema(), type);
180         default -> super.get(property, type);
181      };
182   }
183
184   /**
185    * Bean property getter:  <property>description</property>.
186    *
187    * <p>
188    * A short description of the response.
189    *
190    * @return The property value, or <jk>null</jk> if it is not set.
191    */
192   public String getDescription() { return description; }
193
194   /**
195    * Bean property getter:  <property>examples</property>.
196    *
197    * <p>
198    * An example of the response message.
199    *
200    * @return The property value, or <jk>null</jk> if it is not set.
201    */
202   public Map<String,Object> getExamples() { return u(nie(examples)); }
203
204   /**
205    * Returns the header information with the specified name.
206    *
207    * @param name The header name.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
208    * @return The header info, or <jk>null</jk> if not found.
209    */
210   public HeaderInfo getHeader(String name) {
211      reqnn("name", name);
212      return headers.get(name);
213   }
214
215   /**
216    * Bean property getter:  <property>headers</property>.
217    *
218    * <p>
219    * A list of headers that are sent with the response.
220    *
221    * @return The property value, or <jk>null</jk> if it is not set.
222    */
223   public Map<String,HeaderInfo> getHeaders() { return u(nie(headers)); }
224
225   /**
226    * Bean property getter:  <property>schema</property>.
227    *
228    * <p>
229    * A definition of the response structure.
230    *
231    * @return The property value, or <jk>null</jk> if it is not set.
232    */
233   public SchemaInfo getSchema() { return schema; }
234
235   @Override /* Overridden from SwaggerElement */
236   public Set<String> keySet() {
237      // @formatter:off
238      var s = stb(String.class)
239         .addIf(nn(description), "description")
240         .addIf(ine(examples), "examples")
241         .addIf(ine(headers), "headers")
242         .addIf(nn(schema), "schema")
243         .build();
244      // @formatter:on
245      return new MultiSet<>(s, super.keySet());
246   }
247
248   /**
249    * Resolves any <js>"$ref"</js> attributes in this element.
250    *
251    * @param swagger The swagger document containing the definitions.
252    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
253    * @param maxDepth
254    *    The maximum depth to resolve references.
255    *    <br>After that level is reached, <c>$ref</c> references will be left alone.
256    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
257    * @return
258    *    This object with references resolved.
259    *    <br>May or may not be the same object.
260    */
261   public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
262
263      if (nn(schema))
264         schema = schema.resolveRefs(swagger, refStack, maxDepth);
265
266      headers.entrySet().forEach(x -> x.setValue(x.getValue().resolveRefs(swagger, refStack, maxDepth)));
267
268      return this;
269   }
270
271   @Override /* Overridden from SwaggerElement */
272   public ResponseInfo set(String property, Object value) {
273      reqnn("property", property);
274      return switch (property) {
275         case "description" -> setDescription(s(value));
276         case "examples" -> setExamples(toMapBuilder(value, String.class, Object.class).sparse().build());
277         case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build());
278         case "schema" -> setSchema(toType(value, SchemaInfo.class));
279         default -> {
280            super.set(property, value);
281            yield this;
282         }
283      };
284   }
285
286   /**
287    * Bean property setter:  <property>description</property>.
288    *
289    * <p>
290    * A short description of the response.
291    *
292    * @param value
293    *    The new value for this property.
294    *    <br><a class="doclink" href="https://help.github.com/articles/github-flavored-markdown">GFM syntax</a> can be used for rich text representation.
295    *    <br>Property value is required.
296    *    <br>Can be <jk>null</jk> to unset the property.
297    * @return This object.
298    */
299   public ResponseInfo setDescription(String value) {
300      description = value;
301      return this;
302   }
303
304   /**
305    * Bean property setter:  <property>examples</property>.
306    *
307    * <p>
308    * An example of the response message.
309    *
310    * @param value
311    *    The new value for this property.
312    *    <br>Keys must be MIME-type strings.
313    *    <br>Can be <jk>null</jk> to unset the property.
314    * @return This object.
315    */
316   public ResponseInfo setExamples(Map<String,Object> value) {
317      examples.clear();
318      if (nn(value))
319         examples.putAll(value);
320      return this;
321   }
322
323   /**
324    * Bean property setter:  <property>headers</property>.
325    *
326    * <p>
327    * A list of headers that are sent with the response.
328    *
329    * @param value
330    *    The new value for this property.
331    *    <br>Can be <jk>null</jk> to unset the property.
332    * @return This object.
333    */
334   public ResponseInfo setHeaders(Map<String,HeaderInfo> value) {
335      headers.clear();
336      if (nn(value))
337         headers.putAll(value);
338      return this;
339   }
340
341   /**
342    * Bean property setter:  <property>schema</property>.
343    *
344    * <p>
345    * A definition of the response structure.
346    *
347    * @param value
348    *    The new value for this property.
349    *    <br>It can be a primitive, an array or an object.
350    *    <br>Can be <jk>null</jk> to unset the property.
351    * @return This object.
352    */
353   public ResponseInfo setSchema(SchemaInfo value) {
354      schema = value;
355      return this;
356   }
357
358   /**
359    * Sets strict mode on this bean.
360    *
361    * @return This object.
362    */
363   @Override
364   public ResponseInfo strict() {
365      super.strict();
366      return this;
367   }
368
369   /**
370    * Sets strict mode on this bean.
371    *
372    * @param value
373    *    The new value for this property.
374    *    <br>Non-boolean values will be converted to boolean using <code>Boolean.<jsm>valueOf</jsm>(value.toString())</code>.
375    *    <br>Can be <jk>null</jk> (interpreted as <jk>false</jk>).
376    * @return This object.
377    */
378   @Override
379   public ResponseInfo strict(Object value) {
380      super.strict(value);
381      return this;
382   }
383}