001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.dto.swagger;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017import static org.apache.juneau.internal.ConverterUtils.*;
018
019import java.util.*;
020
021import org.apache.juneau.annotation.*;
022import org.apache.juneau.collections.*;
023import org.apache.juneau.internal.*;
024
025/**
026 * Describes a single response from an API Operation.
027 *
028 * <h5 class='section'>Example:</h5>
029 * <p class='bcode w800'>
030 *    <jc>// Construct using SwaggerBuilder.</jc>
031 *    ResponseInfo x = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>)
032 *       .schema(
033 *          <jsm>schemaInfo</jsm>
034 *             .type(<js>"array"</js>)
035 *             .items(
036 *                <jsm>items</jsm>()
037 *                   .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>)
038 *             )
039 *       );
040 *
041 *    <jc>// Serialize using JsonSerializer.</jc>
042 *    String json = JsonSerializer.<jsf>DEFAULT</jsf>.toString(x);
043 *
044 *    <jc>// Or just use toString() which does the same as above.</jc>
045 *    String json = x.toString();
046 * </p>
047 * <p class='bcode w800'>
048 *    <jc>// Output</jc>
049 *    {
050 *       <js>"description"</js>: <js>"A complex object array response"</js>,
051 *       <js>"schema"</js>: {
052 *          <js>"type"</js>: <js>"array"</js>,
053 *          <js>"items"</js>: {
054 *             <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js>
055 *          }
056 *       }
057 *    }
058 * </p>
059 *
060 * <ul class='seealso'>
061 *    <li class='link'>{@doc DtoSwagger}
062 * </ul>
063 */
064@Bean(bpi="description,schema,headers,x-example,examples,*")
065public class ResponseInfo extends SwaggerElement {
066
067   private String description;
068   private SchemaInfo schema;
069   private Map<String,HeaderInfo> headers;
070   private Object example;
071   private Map<String,Object> examples;
072
073   /**
074    * Default constructor.
075    */
076   public ResponseInfo() {}
077
078   /**
079    * Copy constructor.
080    *
081    * @param copyFrom The object to copy.
082    */
083   public ResponseInfo(ResponseInfo copyFrom) {
084      super(copyFrom);
085
086      this.description = copyFrom.description;
087      this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy();
088
089      if (copyFrom.headers == null) {
090         this.headers = null;
091      } else {
092         this.headers = new LinkedHashMap<>();
093         for (Map.Entry<String,HeaderInfo> e : copyFrom.headers.entrySet())
094            this.headers.put(e.getKey(),  e.getValue().copy());
095      }
096
097      this.example = copyFrom.example;
098
099      if (copyFrom.examples == null)
100         this.examples = null;
101      else
102         this.examples = new LinkedHashMap<>(copyFrom.examples);
103   }
104
105   /**
106    * Make a deep copy of this object.
107    *
108    * @return A deep copy of this object.
109    */
110   public ResponseInfo copy() {
111      return new ResponseInfo(this);
112   }
113
114   /**
115    * Copies any non-null fields from the specified object to this object.
116    *
117    * @param r
118    *    The object to copy fields from.
119    *    <br>Can be <jk>null</jk>.
120    * @return This object (for method chaining).
121    */
122   public ResponseInfo copyFrom(ResponseInfo r) {
123      if (r != null) {
124         if (r.description != null)
125            description = r.description;
126         if (r.schema != null)
127            schema = r.schema;
128         if (r.headers != null)
129            headers = r.headers;
130         if (r.example != null)
131            example = r.example;
132         if (r.examples != null)
133            examples = r.examples;
134      }
135      return this;
136   }
137
138   /**
139    * Bean property getter:  <property>description</property>.
140    *
141    * <p>
142    * A short description of the response.
143    *
144    * @return The property value, or <jk>null</jk> if it is not set.
145    */
146   public String getDescription() {
147      return description;
148   }
149
150   /**
151    * Bean property setter:  <property>description</property>.
152    *
153    * <p>
154    * A short description of the response.
155    *
156    * @param value
157    *    The new value for this property.
158    *    <br>{@doc ExtGFM} can be used for rich text representation.
159    *    <br>Property value is required.
160    * @return This object (for method chaining).
161    */
162   public ResponseInfo setDescription(String value) {
163      description = value;
164      return this;
165   }
166
167   /**
168    * Same as {@link #setDescription(String)}.
169    *
170    * @param value
171    *    The new value for this property.
172    *    <br>Non-String values will be converted to String using <c>toString()</c>.
173    *    <br>Can be <jk>null</jk> to unset the property.
174    * @return This object (for method chaining).
175    */
176   public ResponseInfo description(Object value) {
177      return setDescription(stringify(value));
178   }
179
180   /**
181    * Bean property getter:  <property>schema</property>.
182    *
183    * <p>
184    * A definition of the response structure.
185    *
186    * <ul class='notes'>
187    *    <li>
188    *       If this field does not exist, it means no content is returned as part of the response.
189    *    <li>
190    *       As an extension to the {@doc ExtSwaggerSchemaObject Schema Object},
191    *       its root type value may also be <js>"file"</js>.
192    *    <li>
193    *       This SHOULD be accompanied by a relevant produces mime-type.
194    * </ul>
195    *
196    * @return The property value, or <jk>null</jk> if it is not set.
197    */
198   public SchemaInfo getSchema() {
199      return schema;
200   }
201
202   /**
203    * Bean property setter:  <property>schema</property>.
204    *
205    * <p>
206    * A definition of the response structure.
207    *
208    * <ul class='notes'>
209    *    <li>
210    *       If this field does not exist, it means no content is returned as part of the response.
211    *    <li>
212    *       As an extension to the {@doc ExtSwaggerSchemaObject Schema Object},
213    *       its root type value may also be <js>"file"</js>.
214    *    <li>
215    *       This SHOULD be accompanied by a relevant produces mime-type.
216    * </ul>
217    *
218    * @param value
219    *    The new value for this property.
220    *    <br>It can be a primitive, an array or an object.
221    *    <br>Can be <jk>null</jk> to unset the property.
222    * @return This object (for method chaining).
223    */
224   public ResponseInfo setSchema(SchemaInfo value) {
225      schema = value;
226      return this;
227   }
228
229   /**
230    * Same as {@link #setSchema(SchemaInfo)}.
231    *
232    * @param value
233    *    The new value for this property.
234    *    <br>Valid types:
235    *    <ul>
236    *       <li>{@link SchemaInfo}
237    *       <li><c>String</c> - JSON object representation of {@link SchemaInfo}
238    *          <p class='bcode w800'>
239    *    <jc>// Example </jc>
240    *    schema(<js>"{type:'type',description:'description',...}"</js>);
241    *          </p>
242    *    </ul>
243    *    <br>Can be <jk>null</jk> to unset the property.
244    * @return This object (for method chaining).
245    */
246   public ResponseInfo schema(Object value) {
247      return setSchema(toType(value, SchemaInfo.class));
248   }
249
250   /**
251    * Bean property getter:  <property>headers</property>.
252    *
253    * <p>
254    * A list of headers that are sent with the response.
255    *
256    * @return The property value, or <jk>null</jk> if it is not set.
257    */
258   public Map<String,HeaderInfo> getHeaders() {
259      return headers;
260   }
261
262   /**
263    * Bean property setter:  <property>headers</property>.
264    *
265    * <p>
266    * A list of headers that are sent with the response.
267    *
268    * @param value
269    *    The new value for this property.
270    *    <br>Can be <jk>null</jk> to unset the property.
271    * @return This object (for method chaining).
272    */
273   public ResponseInfo setHeaders(Map<String,HeaderInfo> value) {
274      headers = newMap(value);
275      return this;
276   }
277
278   /**
279    * Adds one or more values to the <property>headers</property> property.
280    *
281    * @param values
282    *    The values to add to this property.
283    *    <br>Ignored if <jk>null</jk>.
284    * @return This object (for method chaining).
285    */
286   public ResponseInfo addHeaders(Map<String,HeaderInfo> values) {
287      headers = addToMap(headers, values);
288      return this;
289   }
290
291   /**
292    * Adds a single value to the <property>headers</property> property.
293    *
294    * @param name The header name.
295    * @param header The header descriptions
296    * @return This object (for method chaining).
297    */
298   public ResponseInfo header(String name, HeaderInfo header) {
299      addHeaders(Collections.singletonMap(name, header));
300      return this;
301   }
302
303   /**
304    * Adds one or more values to the <property>headers</property> property.
305    *
306    * @param values
307    *    The values to add to this property.
308    *    <br>Valid types:
309    *    <ul>
310    *       <li><c>Map&lt;String,{@link HeaderInfo}|String&gt;</c>
311    *       <li><c>String</c> - JSON object representation of <c>Map&lt;String,{@link HeaderInfo}&gt;</c>
312    *          <p class='bcode w800'>
313    *    <jc>// Example </jc>
314    *    headers(<js>"{headerName:{description:'description',...}}"</js>);
315    *          </p>
316    *    </ul>
317    *    <br>Ignored if <jk>null</jk>.
318    * @return This object (for method chaining).
319    */
320   public ResponseInfo headers(Object...values) {
321      headers = addToMap(headers, values, String.class, HeaderInfo.class);
322      return this;
323   }
324
325   /**
326    * Returns the header information with the specified name.
327    *
328    * @param name The header name.
329    * @return The header info, or <jk>null</jk> if not found.
330    */
331   public HeaderInfo getHeader(String name) {
332      return getHeaders().get(name);
333   }
334
335   /**
336    * Bean property getter:  <property>x-example</property>.
337    *
338    * @return The property value, or <jk>null</jk> if it is not set.
339    */
340   @Beanp("x-example")
341   public Object getExample() {
342      return example;
343   }
344
345   /**
346    * Bean property setter:  <property>x-example</property>.
347    *
348    * @param value
349    *    The new value for this property.
350    *    <br>Can be <jk>null</jk> to unset the property.
351    * @return This object (for method chaining).
352    */
353   @Beanp("x-example")
354   public ResponseInfo setExample(Object value) {
355      example = value;
356      return this;
357   }
358
359   /**
360    * Bean property setter:  <property>x-example</property>.
361    *
362    * @param value The property value.
363    * @return This object (for method chaining).
364    */
365   public ResponseInfo example(Object value) {
366      example = value;
367      return this;
368   }
369
370   /**
371    * Bean property getter:  <property>examples</property>.
372    *
373    * <p>
374    * An example of the response message.
375    *
376    * @return The property value, or <jk>null</jk> if it is not set.
377    */
378   public Map<String,Object> getExamples() {
379      return examples;
380   }
381
382   /**
383    * Bean property setter:  <property>examples</property>.
384    *
385    * <p>
386    * An example of the response message.
387    *
388    * @param value
389    *    The new value for this property.
390    *    <br>Keys must be MIME-type strings.
391    *    <br>Can be <jk>null</jk> to unset the property.
392    * @return This object (for method chaining).
393    */
394   public ResponseInfo setExamples(Map<String,Object> value) {
395      examples = newMap(value);
396      return this;
397   }
398
399   /**
400    * Adds one or more values to the <property>examples</property> property.
401    *
402    * @param values
403    *    The values to add to this property.
404    *    <br>Ignored if <jk>null</jk>.
405    * @return This object (for method chaining).
406    */
407   public ResponseInfo addExamples(Map<String,Object> values) {
408      examples = addToMap(examples, values);
409      return this;
410   }
411
412   /**
413    * Adds a single value to the <property>examples</property> property.
414    *
415    * @param mimeType The mime-type string.
416    * @param example The example.
417    * @return This object (for method chaining).
418    */
419   public ResponseInfo example(String mimeType, Object example) {
420      examples = addToMap(examples, mimeType, example);
421      return this;
422   }
423
424   /**
425    * Adds one or more values to the <property>examples</property> property.
426    *
427    * @param values
428    *    The values to add to this property.
429    *    <br>Valid types:
430    *    <ul>
431    *       <li><c>Map&lt;String,Object&gt;</c>
432    *       <li><c>String</c> - JSON object representation of <c>Map&lt;String,Object&gt;</c>
433    *          <p class='bcode w800'>
434    *    <jc>// Example </jc>
435    *    examples(<js>"{'text/json':{foo:'bar'}}"</js>);
436    *          </p>
437    *    </ul>
438    *    <br>Ignored if <jk>null</jk>.
439    * @return This object (for method chaining).
440    */
441   public ResponseInfo examples(Object...values) {
442      examples = addToMap(examples, values, String.class, Object.class);
443      return this;
444   }
445
446   @Override /* SwaggerElement */
447   public <T> T get(String property, Class<T> type) {
448      if (property == null)
449         return null;
450      switch (property) {
451         case "description": return toType(getDescription(), type);
452         case "schema": return toType(getSchema(), type);
453         case "headers": return toType(getHeaders(), type);
454         case "example": return toType(getExample(), type);
455         case "examples": return toType(getExamples(), type);
456         default: return super.get(property, type);
457      }
458   }
459
460   @Override /* SwaggerElement */
461   public ResponseInfo set(String property, Object value) {
462      if (property == null)
463         return this;
464      switch (property) {
465         case "description": return description(value);
466         case "schema": return schema(value);
467         case "headers": return setHeaders(null).headers(value);
468         case "example": return setExample(value);
469         case "examples": return setExamples(null).examples(value);
470         default:
471            super.set(property, value);
472            return this;
473      }
474   }
475
476   @Override /* SwaggerElement */
477   public Set<String> keySet() {
478      ASet<String> s = ASet.<String>of()
479         .aif(description != null, "description")
480         .aif(schema != null, "schema")
481         .aif(headers != null, "headers")
482         .aif(example != null, "example")
483         .aif(examples != null, "examples");
484      return new MultiSet<>(s, super.keySet());
485   }
486
487   /**
488    * Returns <jk>true</jk> if this response info has headers associated with it.
489    *
490    * @return <jk>true</jk> if this response info has headers associated with it.
491    */
492   public boolean hasHeaders() {
493      return headers != null && ! headers.isEmpty();
494   }
495
496   /**
497    * Resolves any <js>"$ref"</js> attributes in this element.
498    *
499    * @param swagger The swagger document containing the definitions.
500    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
501    * @param maxDepth
502    *    The maximum depth to resolve references.
503    *    <br>After that level is reached, <c>$ref</c> references will be left alone.
504    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
505    * @return
506    *    This object with references resolved.
507    *    <br>May or may not be the same object.
508    */
509   public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
510
511      if (schema != null)
512         schema = schema.resolveRefs(swagger, refStack, maxDepth);
513
514      if (headers != null)
515         for (Map.Entry<String,HeaderInfo> e : headers.entrySet())
516            e.setValue(e.getValue().resolveRefs(swagger, refStack, maxDepth));
517
518      return this;
519   }
520}