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.common.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.internal.*;
023
024/**
025 * Describes a single response from an API Operation.
026 *
027 * <h5 class='section'>Example:</h5>
028 * <p class='bjava'>
029 *    <jc>// Construct using SwaggerBuilder.</jc>
030 *    ResponseInfo <jv>info</jv> = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>)
031 *       .schema(
032 *          <jsm>schemaInfo</jsm>
033 *             .type(<js>"array"</js>)
034 *             .items(
035 *                <jsm>items</jsm>()
036 *                   .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>)
037 *             )
038 *       );
039 *
040 *    <jc>// Serialize using JsonSerializer.</jc>
041 *    String <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.toString(<jv>info</jv>);
042 *
043 *    <jc>// Or just use toString() which does the same as above.</jc>
044 *    <jv>json</jv> = <jv>info</jv>.toString();
045 * </p>
046 * <p class='bjson'>
047 *    <jc>// Output</jc>
048 *    {
049 *       <js>"description"</js>: <js>"A complex object array response"</js>,
050 *       <js>"schema"</js>: {
051 *          <js>"type"</js>: <js>"array"</js>,
052 *          <js>"items"</js>: {
053 *             <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js>
054 *          }
055 *       }
056 *    }
057 * </p>
058 *
059 * <h5 class='section'>See Also:</h5><ul>
060 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.Swagger">Overview &gt; juneau-rest-server &gt; Swagger</a>
061 * </ul>
062 */
063@Bean(properties="description,schema,headers,examples,*")
064@FluentSetters
065public class ResponseInfo extends SwaggerElement {
066
067   private String description;
068   private SchemaInfo schema;
069   private Map<String,HeaderInfo> headers;
070   private Map<String,Object> examples;
071
072   /**
073    * Default constructor.
074    */
075   public ResponseInfo() {}
076
077   /**
078    * Copy constructor.
079    *
080    * @param copyFrom The object to copy.
081    */
082   public ResponseInfo(ResponseInfo copyFrom) {
083      super(copyFrom);
084
085      this.description = copyFrom.description;
086      this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy();
087
088      this.examples = copyOf(copyFrom.examples);
089
090      if (copyFrom.headers == null) {
091         this.headers = null;
092      } else {
093         this.headers = map();
094         copyFrom.headers.forEach((k,v) -> this.headers.put(k, v.copy()));
095      }
096
097   }
098
099   /**
100    * Make a deep copy of this object.
101    *
102    * @return A deep copy of this object.
103    */
104   public ResponseInfo copy() {
105      return new ResponseInfo(this);
106   }
107
108   /**
109    * Copies any non-null fields from the specified object to this object.
110    *
111    * @param r
112    *    The object to copy fields from.
113    *    <br>Can be <jk>null</jk>.
114    * @return This object.
115    */
116   public ResponseInfo copyFrom(ResponseInfo r) {
117      if (r != null) {
118         if (r.description != null)
119            description = r.description;
120         if (r.schema != null)
121            schema = r.schema;
122         if (r.headers != null)
123            headers = r.headers;
124         if (r.examples != null)
125            examples = r.examples;
126      }
127      return this;
128   }
129
130   //-----------------------------------------------------------------------------------------------------------------
131   // Properties
132   //-----------------------------------------------------------------------------------------------------------------
133
134   /**
135    * Bean property getter:  <property>description</property>.
136    *
137    * <p>
138    * A short description of the response.
139    *
140    * @return The property value, or <jk>null</jk> if it is not set.
141    */
142   public String getDescription() {
143      return description;
144   }
145
146   /**
147    * Bean property setter:  <property>description</property>.
148    *
149    * <p>
150    * A short description of the response.
151    *
152    * @param value
153    *    The new value for this property.
154    *    <br><a class="doclink" href="https://help.github.com/articles/github-flavored-markdown">GFM syntax</a> can be used for rich text representation.
155    *    <br>Property value is required.
156    * @return This object.
157    */
158   public ResponseInfo setDescription(String value) {
159      description = value;
160      return this;
161   }
162
163   /**
164    * Bean property getter:  <property>examples</property>.
165    *
166    * <p>
167    * An example of the response message.
168    *
169    * @return The property value, or <jk>null</jk> if it is not set.
170    */
171   public Map<String,Object> getExamples() {
172      return examples;
173   }
174
175   /**
176    * Bean property setter:  <property>examples</property>.
177    *
178    * <p>
179    * An example of the response message.
180    *
181    * @param value
182    *    The new value for this property.
183    *    <br>Keys must be MIME-type strings.
184    *    <br>Can be <jk>null</jk> to unset the property.
185    * @return This object.
186    */
187   public ResponseInfo setExamples(Map<String,Object> value) {
188      examples = copyOf(value);
189      return this;
190   }
191
192   /**
193    * Bean property appender:  <property>examples</property>.
194    *
195    * <p>
196    * Adds a single value to the <property>examples</property> property.
197    *
198    * @param mimeType The mime-type string.
199    * @param example The example.
200    * @return This object.
201    */
202   public ResponseInfo addExample(String mimeType, Object example) {
203      examples =  mapBuilder(examples).sparse().add(mimeType, example).build();
204      return this;
205   }
206
207   /**
208    * Bean property getter:  <property>headers</property>.
209    *
210    * <p>
211    * A list of headers that are sent with the response.
212    *
213    * @return The property value, or <jk>null</jk> if it is not set.
214    */
215   public Map<String,HeaderInfo> getHeaders() {
216      return headers;
217   }
218
219   /**
220    * Bean property setter:  <property>headers</property>.
221    *
222    * <p>
223    * A list of headers that are sent with the response.
224    *
225    * @param value
226    *    The new value for this property.
227    *    <br>Can be <jk>null</jk> to unset the property.
228    * @return This object.
229    */
230   public ResponseInfo setHeaders(Map<String,HeaderInfo> value) {
231      headers = copyOf(value);
232      return this;
233   }
234
235   /**
236    * Bean property appender:  <property>headers</property>.
237    *
238    * @param name The header name.
239    * @param header The header descriptions
240    * @return This object.
241    */
242   public ResponseInfo addHeader(String name, HeaderInfo header) {
243      headers = mapBuilder(headers).add(name, header).build();
244      return this;
245   }
246
247   /**
248    * Returns the header information with the specified name.
249    *
250    * @param name The header name.
251    * @return The header info, or <jk>null</jk> if not found.
252    */
253   public HeaderInfo getHeader(String name) {
254      return getHeaders().get(name);
255   }
256
257   /**
258    * Bean property getter:  <property>schema</property>.
259    *
260    * <p>
261    * A definition of the response structure.
262    *
263    * @return The property value, or <jk>null</jk> if it is not set.
264    */
265   public SchemaInfo getSchema() {
266      return schema;
267   }
268
269   /**
270    * Bean property setter:  <property>schema</property>.
271    *
272    * <p>
273    * A definition of the response structure.
274    *
275    * @param value
276    *    The new value for this property.
277    *    <br>It can be a primitive, an array or an object.
278    *    <br>Can be <jk>null</jk> to unset the property.
279    * @return This object.
280    */
281   public ResponseInfo setSchema(SchemaInfo value) {
282      schema = value;
283      return this;
284   }
285
286   // <FluentSetters>
287
288   // </FluentSetters>
289
290   @Override /* SwaggerElement */
291   public <T> T get(String property, Class<T> type) {
292      if (property == null)
293         return null;
294      switch (property) {
295         case "description": return toType(getDescription(), type);
296         case "examples": return toType(getExamples(), type);
297         case "headers": return toType(getHeaders(), type);
298         case "schema": return toType(getSchema(), type);
299         default: return super.get(property, type);
300      }
301   }
302
303   @Override /* SwaggerElement */
304   public ResponseInfo set(String property, Object value) {
305      if (property == null)
306         return this;
307      switch (property) {
308         case "description": return setDescription(stringify(value));
309         case "examples": return setExamples(mapBuilder(String.class,Object.class).sparse().addAny(value).build());
310         case "headers": return setHeaders(mapBuilder(String.class,HeaderInfo.class).sparse().addAny(value).build());
311         case "schema": return setSchema(toType(value, SchemaInfo.class));
312         default:
313            super.set(property, value);
314            return this;
315      }
316   }
317
318   @Override /* SwaggerElement */
319   public Set<String> keySet() {
320      Set<String> s = setBuilder(String.class)
321         .addIf(description != null, "description")
322         .addIf(examples != null, "examples")
323         .addIf(headers != null, "headers")
324         .addIf(schema != null, "schema")
325         .build();
326      return new MultiSet<>(s, super.keySet());
327   }
328
329   /**
330    * Resolves any <js>"$ref"</js> attributes in this element.
331    *
332    * @param swagger The swagger document containing the definitions.
333    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
334    * @param maxDepth
335    *    The maximum depth to resolve references.
336    *    <br>After that level is reached, <c>$ref</c> references will be left alone.
337    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
338    * @return
339    *    This object with references resolved.
340    *    <br>May or may not be the same object.
341    */
342   public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
343
344      if (schema != null)
345         schema = schema.resolveRefs(swagger, refStack, maxDepth);
346
347      if (headers != null)
348         headers.entrySet().forEach(x -> x.setValue(x.getValue().resolveRefs(swagger, refStack, maxDepth)));
349
350      return this;
351   }
352}