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.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.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 */
083public class ResponseInfo extends SwaggerElement {
084
085   private String description;
086   private SchemaInfo schema;
087   private Map<String,HeaderInfo> headers = map();
088   private Map<String,Object> examples = map();
089
090   /**
091    * Default constructor.
092    */
093   public ResponseInfo() {}
094
095   /**
096    * Copy constructor.
097    *
098    * @param copyFrom The object to copy.
099    */
100   public ResponseInfo(ResponseInfo copyFrom) {
101      super(copyFrom);
102
103      this.description = copyFrom.description;
104      this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy();
105      if (nn(copyFrom.examples))
106         examples.putAll(copyOf(copyFrom.examples));
107      if (nn(copyFrom.headers))
108         headers.putAll(copyOf(copyFrom.headers, HeaderInfo::copy));
109   }
110
111   /**
112    * Bean property appender:  <property>examples</property>.
113    *
114    * <p>
115    * Adds a single value to the <property>examples</property> property.
116    *
117    * @param mimeType The mime-type string.  Must not be <jk>null</jk>.
118    * @param example The example.  Must not be <jk>null</jk>.
119    * @return This object.
120    */
121   public ResponseInfo addExample(String mimeType, Object example) {
122      assertArgNotNull("mimeType", mimeType);
123      assertArgNotNull("example", example);
124      examples.put(mimeType, example);
125      return this;
126   }
127
128   /**
129    * Bean property appender:  <property>headers</property>.
130    *
131    * @param name The header name.  Must not be <jk>null</jk>.
132    * @param header The header descriptions  Must not be <jk>null</jk>.
133    * @return This object.
134    */
135   public ResponseInfo addHeader(String name, HeaderInfo header) {
136      assertArgNotNull("name", name);
137      assertArgNotNull("header", header);
138      headers.put(name, header);
139      return this;
140   }
141
142   /**
143    * Make a deep copy of this object.
144    *
145    * @return A deep copy of this object.
146    */
147   public ResponseInfo copy() {
148      return new ResponseInfo(this);
149   }
150
151   /**
152    * Copies any non-null fields from the specified object to this object.
153    *
154    * @param r
155    *    The object to copy fields from.
156    *    <br>Can be <jk>null</jk>.
157    * @return This object.
158    */
159   public ResponseInfo copyFrom(ResponseInfo r) {
160      if (nn(r)) {
161         if (nn(r.description))
162            description = r.description;
163         if (nn(r.schema))
164            schema = r.schema;
165         if (nn(r.headers))
166            headers = r.headers;
167         if (nn(r.examples))
168            examples = r.examples;
169      }
170      return this;
171   }
172
173   @Override /* Overridden from SwaggerElement */
174   public <T> T get(String property, Class<T> type) {
175      assertArgNotNull("property", property);
176      return switch (property) {
177         case "description" -> toType(getDescription(), type);
178         case "examples" -> toType(getExamples(), type);
179         case "headers" -> toType(getHeaders(), type);
180         case "schema" -> toType(getSchema(), type);
181         default -> super.get(property, type);
182      };
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    * Bean property getter:  <property>examples</property>.
197    *
198    * <p>
199    * An example of the response message.
200    *
201    * @return The property value, or <jk>null</jk> if it is not set.
202    */
203   public Map<String,Object> getExamples() { return nullIfEmpty(examples); }
204
205   /**
206    * Returns the header information with the specified name.
207    *
208    * @param name The header name.  Must not be <jk>null</jk>.
209    * @return The header info, or <jk>null</jk> if not found.
210    */
211   public HeaderInfo getHeader(String name) {
212      assertArgNotNull("name", name);
213      return headers.get(name);
214   }
215
216   /**
217    * Bean property getter:  <property>headers</property>.
218    *
219    * <p>
220    * A list of headers that are sent with the response.
221    *
222    * @return The property value, or <jk>null</jk> if it is not set.
223    */
224   public Map<String,HeaderInfo> getHeaders() { return nullIfEmpty(headers); }
225
226   /**
227    * Bean property getter:  <property>schema</property>.
228    *
229    * <p>
230    * A definition of the response structure.
231    *
232    * @return The property value, or <jk>null</jk> if it is not set.
233    */
234   public SchemaInfo getSchema() { return schema; }
235
236   @Override /* Overridden from SwaggerElement */
237   public Set<String> keySet() {
238      // @formatter:off
239      var s = setb(String.class)
240         .addIf(nn(description), "description")
241         .addIf(ne(examples), "examples")
242         .addIf(ne(headers), "headers")
243         .addIf(nn(schema), "schema")
244         .build();
245      // @formatter:on
246      return new MultiSet<>(s, super.keySet());
247   }
248
249   /**
250    * Resolves any <js>"$ref"</js> attributes in this element.
251    *
252    * @param swagger The swagger document containing the definitions.
253    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
254    * @param maxDepth
255    *    The maximum depth to resolve references.
256    *    <br>After that level is reached, <c>$ref</c> references will be left alone.
257    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
258    * @return
259    *    This object with references resolved.
260    *    <br>May or may not be the same object.
261    */
262   public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
263
264      if (nn(schema))
265         schema = schema.resolveRefs(swagger, refStack, maxDepth);
266
267      headers.entrySet().forEach(x -> x.setValue(x.getValue().resolveRefs(swagger, refStack, maxDepth)));
268
269      return this;
270   }
271
272   @Override /* Overridden from SwaggerElement */
273   public ResponseInfo set(String property, Object value) {
274      assertArgNotNull("property", property);
275      return switch (property) {
276         case "description" -> setDescription(s(value));
277         case "examples" -> setExamples(toMapBuilder(value, String.class, Object.class).sparse().build());
278         case "headers" -> setHeaders(toMapBuilder(value, String.class, HeaderInfo.class).sparse().build());
279         case "schema" -> setSchema(toType(value, SchemaInfo.class));
280         default -> {
281            super.set(property, value);
282            yield this;
283         }
284      };
285   }
286
287   /**
288    * Bean property setter:  <property>description</property>.
289    *
290    * <p>
291    * A short description of the response.
292    *
293    * @param value
294    *    The new value for this property.
295    *    <br><a class="doclink" href="https://help.github.com/articles/github-flavored-markdown">GFM syntax</a> can be used for rich text representation.
296    *    <br>Property value is required.
297    *    <br>Can be <jk>null</jk> to unset the property.
298    * @return This object.
299    */
300   public ResponseInfo setDescription(String value) {
301      description = value;
302      return this;
303   }
304
305   /**
306    * Bean property setter:  <property>examples</property>.
307    *
308    * <p>
309    * An example of the response message.
310    *
311    * @param value
312    *    The new value for this property.
313    *    <br>Keys must be MIME-type strings.
314    *    <br>Can be <jk>null</jk> to unset the property.
315    * @return This object.
316    */
317   public ResponseInfo setExamples(Map<String,Object> value) {
318      examples.clear();
319      if (nn(value))
320         examples.putAll(value);
321      return this;
322   }
323
324   /**
325    * Bean property setter:  <property>headers</property>.
326    *
327    * <p>
328    * A list of headers that are sent with the response.
329    *
330    * @param value
331    *    The new value for this property.
332    *    <br>Can be <jk>null</jk> to unset the property.
333    * @return This object.
334    */
335   public ResponseInfo setHeaders(Map<String,HeaderInfo> value) {
336      headers.clear();
337      if (nn(value))
338         headers.putAll(value);
339      return this;
340   }
341
342   /**
343    * Bean property setter:  <property>schema</property>.
344    *
345    * <p>
346    * A definition of the response structure.
347    *
348    * @param value
349    *    The new value for this property.
350    *    <br>It can be a primitive, an array or an object.
351    *    <br>Can be <jk>null</jk> to unset the property.
352    * @return This object.
353    */
354   public ResponseInfo setSchema(SchemaInfo value) {
355      schema = value;
356      return this;
357   }
358
359   /**
360    * Sets strict mode on this bean.
361    *
362    * @return This object.
363    */
364   @Override
365   public ResponseInfo strict() {
366      super.strict();
367      return this;
368   }
369
370   /**
371    * Sets strict mode on this bean.
372    *
373    * @param value
374    *    The new value for this property.
375    *    <br>Non-boolean values will be converted to boolean using <code>Boolean.<jsm>valueOf</jsm>(value.toString())</code>.
376    *    <br>Can be <jk>null</jk> (interpreted as <jk>false</jk>).
377    * @return This object.
378    */
379   @Override
380   public ResponseInfo strict(Object value) {
381      super.strict(value);
382      return this;
383   }
384}