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