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.ThrowableUtils.*;
022import static org.apache.juneau.commons.utils.Utils.*;
023import static org.apache.juneau.internal.ConverterUtils.*;
024
025import java.util.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.collections.*;
029import org.apache.juneau.commons.collections.*;
030import org.apache.juneau.json.*;
031import org.apache.juneau.objecttools.*;
032
033/**
034 * This is the root document object for the Swagger 2.0 API specification.
035 *
036 * <p>
037 * The Swagger Object is the root document that describes an entire API. It contains metadata about the API,
038 * available paths and operations, parameters, responses, security definitions, and other information. This is
039 * the Swagger 2.0 specification (predecessor to OpenAPI 3.0).
040 *
041 * <h5 class='section'>Swagger Specification:</h5>
042 * <p>
043 * The Swagger Object is composed of the following fields:
044 * <ul class='spaced-list'>
045 *    <li><c>swagger</c> (string, REQUIRED) - The Swagger Specification version (must be <js>"2.0"</js>)
046 *    <li><c>info</c> ({@link Info}, REQUIRED) - Provides metadata about the API
047 *    <li><c>host</c> (string) - The host (name or IP) serving the API
048 *    <li><c>basePath</c> (string) - The base path on which the API is served (relative to host)
049 *    <li><c>schemes</c> (array of string) - The transfer protocols of the API (e.g., <js>"http"</js>, <js>"https"</js>)
050 *    <li><c>consumes</c> (array of string) - A list of MIME types the APIs can consume
051 *    <li><c>produces</c> (array of string) - A list of MIME types the APIs can produce
052 *    <li><c>paths</c> (map of {@link OperationMap}, REQUIRED) - The available paths and operations for the API
053 *    <li><c>definitions</c> (map of {@link SchemaInfo}) - Schema definitions that can be referenced
054 *    <li><c>parameters</c> (map of {@link ParameterInfo}) - Parameters definitions that can be referenced
055 *    <li><c>responses</c> (map of {@link ResponseInfo}) - Response definitions that can be referenced
056 *    <li><c>securityDefinitions</c> (map of {@link SecurityScheme}) - Security scheme definitions
057 *    <li><c>security</c> (array of map) - Security requirements applied to all operations
058 *    <li><c>tags</c> (array of {@link Tag}) - A list of tags used by the specification with additional metadata
059 *    <li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation
060 * </ul>
061 *
062 * <h5 class='section'>Example:</h5>
063 * <p class='bjava'>
064 *    <jc>// Create a Swagger document</jc>
065 *    Swagger <jv>doc</jv> = <jk>new</jk> Swagger()
066 *       .setSwagger(<js>"2.0"</js>)
067 *       .setInfo(
068 *          <jk>new</jk> Info()
069 *             .setTitle(<js>"Pet Store API"</js>)
070 *             .setVersion(<js>"1.0.0"</js>)
071 *       )
072 *       .setHost(<js>"petstore.swagger.io"</js>)
073 *       .setBasePath(<js>"/v2"</js>)
074 *       .setSchemes(<js>"https"</js>)
075 *       .addPath(<js>"/pets"</js>, <js>"get"</js>,
076 *          <jk>new</jk> Operation()
077 *             .setSummary(<js>"List all pets"</js>)
078 *             .addResponse(<js>"200"</js>, <jk>new</jk> ResponseInfo(<js>"Success"</js>))
079 *       );
080 * </p>
081 *
082 * <h5 class='section'>See Also:</h5><ul>
083 *    <li class='link'><a class="doclink" href="https://swagger.io/specification/v2/">Swagger 2.0 Specification</a>
084 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/2-0/basic-structure/">Swagger Basic Structure</a>
085 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanSwagger2">juneau-bean-swagger-v2</a>
086 * </ul>
087 */
088public class Swagger extends SwaggerElement {
089
090   private static interface MapOfStringLists extends Map<String,List<String>> {}
091
092   /** Represents a null swagger */
093   public static final Swagger NULL = new Swagger();
094
095   private static final Comparator<String> PATH_COMPARATOR = (o1, o2) -> o1.replace('{', '@').compareTo(o2.replace('{', '@'));
096   private String swagger = "2.0",  // NOSONAR - Intentional naming.
097      host, basePath;
098   private Info info;
099   private ExternalDocumentation externalDocs;
100   private Set<String> schemes = new LinkedHashSet<>();
101   private Set<MediaType> consumes = new LinkedHashSet<>(), produces = new LinkedHashSet<>();
102   private Set<Tag> tags = new LinkedHashSet<>();
103   private List<Map<String,List<String>>> security = list();
104   private Map<String,JsonMap> definitions = map();
105   private Map<String,ParameterInfo> parameters = map();
106   private Map<String,ResponseInfo> responses = map();
107   private Map<String,SecurityScheme> securityDefinitions = map();
108
109   private Map<String,OperationMap> paths = new TreeMap<>(PATH_COMPARATOR);
110
111   /**
112    * Default constructor.
113    */
114   public Swagger() {}
115
116   /**
117    * Copy constructor.
118    *
119    * @param copyFrom The object to copy.
120    */
121   public Swagger(Swagger copyFrom) {
122      super(copyFrom);
123
124      this.basePath = copyFrom.basePath;
125      if (nn(copyFrom.consumes))
126         this.consumes.addAll(copyOf(copyFrom.consumes));
127      this.externalDocs = copyFrom.externalDocs == null ? null : copyFrom.externalDocs.copy();
128      this.host = copyFrom.host;
129      this.info = copyFrom.info == null ? null : copyFrom.info.copy();
130      if (nn(copyFrom.produces))
131         this.produces.addAll(copyOf(copyFrom.produces));
132      if (nn(copyFrom.schemes))
133         this.schemes.addAll(copyOf(copyFrom.schemes));
134      this.swagger = copyFrom.swagger;
135
136      // TODO - Definitions are not deep copied, so they should not contain references.
137      if (nn(copyFrom.definitions))
138         definitions.putAll(copyOf(copyFrom.definitions, JsonMap::new));
139
140      if (nn(copyFrom.paths))
141         copyFrom.paths.forEach((k, v) -> {
142            var m = new OperationMap();
143            v.forEach((k2, v2) -> m.put(k2, v2.copy()));
144            paths.put(k, m);
145         });
146
147      if (nn(copyFrom.parameters))
148         parameters.putAll(copyOf(copyFrom.parameters, ParameterInfo::copy));
149      if (nn(copyFrom.responses))
150         responses.putAll(copyOf(copyFrom.responses, ResponseInfo::copy));
151      if (nn(copyFrom.securityDefinitions))
152         securityDefinitions.putAll(copyOf(copyFrom.securityDefinitions, SecurityScheme::copy));
153
154      if (nn(copyFrom.security))
155         copyFrom.security.forEach(x -> {
156            Map<String,List<String>> m2 = map();
157            x.forEach((k, v) -> m2.put(k, copyOf(v)));
158            security.add(m2);
159         });
160
161      if (nn(copyFrom.tags))
162         this.tags.addAll(copyOf(copyFrom.tags, x -> x.copy()));
163   }
164
165   /**
166    * Bean property appender:  <property>consumes</property>.
167    *
168    * <p>
169    * A list of MIME types the APIs can consume.
170    *
171    * @param values
172    *    The values to add to this property.
173    *    <br>Ignored if <jk>null</jk>.
174    * @return This object.
175    */
176   public Swagger addConsumes(Collection<MediaType> values) {
177      if (nn(values))
178         consumes.addAll(values);
179      return this;
180   }
181
182   /**
183    * Bean property appender:  <property>consumes</property>.
184    *
185    * <p>
186    * A list of MIME types the APIs can consume.
187    *
188    * @param values
189    *    The values to add to this property.
190    *    <br>Values MUST be as described under <a class="doclink" href="https://swagger.io/specification#mimeTypes">Swagger Mime Types</a>.
191    *    <br>Ignored if <jk>null</jk>.
192    * @return This object.
193    */
194   public Swagger addConsumes(MediaType...values) {
195      if (nn(values))
196         for (var v : values)
197            if (nn(v))
198               consumes.add(v);
199      return this;
200   }
201
202   /**
203    * Bean property appender:  <property>definitions</property>.
204    *
205    * <p>
206    * Adds a single value to the <property>definitions</property> property.
207    *
208    * @param name A definition name.  Must not be <jk>null</jk>.
209    * @param schema The schema that the name defines.  Must not be <jk>null</jk>.
210    * @return This object.
211    */
212   public Swagger addDefinition(String name, JsonMap schema) {
213      assertArgNotNull("name", name);
214      assertArgNotNull("schema", schema);
215      definitions.put(name, schema);
216      return this;
217   }
218
219   /**
220    * Bean property appender:  <property>parameters</property>.
221    *
222    * <p>
223    * Adds a single value to the <property>parameter</property> property.
224    *
225    * @param name The parameter name.  Must not be <jk>null</jk>.
226    * @param parameter The parameter definition.  Must not be <jk>null</jk>.
227    * @return This object.
228    */
229   public Swagger addParameter(String name, ParameterInfo parameter) {
230      assertArgNotNull("name", name);
231      assertArgNotNull("parameter", parameter);
232      parameters.put(name, parameter);
233      return this;
234   }
235
236   /**
237    * Bean property appender:  <property>paths</property>.
238    *
239    * <p>
240    * Adds a single value to the <property>paths</property> property.
241    *
242    * @param path The path template.  Must not be <jk>null</jk>.
243    * @param methodName The HTTP method name.  Must not be <jk>null</jk>.
244    * @param operation The operation that describes the path.  Must not be <jk>null</jk>.
245    * @return This object.
246    */
247   public Swagger addPath(String path, String methodName, Operation operation) {
248      assertArgNotNull("path", path);
249      assertArgNotNull("methodName", methodName);
250      assertArgNotNull("operation", operation);
251      paths.computeIfAbsent(path, k -> new OperationMap()).put(methodName, operation);
252      return this;
253   }
254
255   /**
256    * Bean property appender:  <property>produces</property>.
257    *
258    * <p>
259    * A list of MIME types the APIs can produce.
260    *
261    * @param values
262    *    The values to add to this property.
263    *    <br>Value MUST be as described under <a class="doclink" href="https://swagger.io/specification#mimeTypes">Swagger Mime Types</a>.
264    *    <br>Ignored if <jk>null</jk>.
265    * @return This object.
266    */
267   public Swagger addProduces(Collection<MediaType> values) {
268      if (nn(values))
269         produces.addAll(values);
270      return this;
271   }
272
273   /**
274    * Adds one or more values to the <property>produces</property> property.
275    *
276    * <p>
277    * A list of MIME types the APIs can produce.
278    *
279    * @param values
280    *    The values to add to this property.
281    *    <br>Value MUST be as described under <a class="doclink" href="https://swagger.io/specification#mimeTypes">Swagger Mime Types</a>.
282    *    <br>Can be <jk>null</jk> to unset the property.
283    * @return This object.
284    */
285   public Swagger addProduces(MediaType...values) {
286      if (nn(values))
287         for (var v : values)
288            if (nn(v))
289               produces.add(v);
290      return this;
291   }
292
293   /**
294    * Bean property appender:  <property>responses</property>.
295    *
296    * <p>
297    * Adds a single value to the <property>responses</property> property.
298    *
299    * @param name The response name.  Must not be <jk>null</jk>.
300    * @param response The response definition.  Must not be <jk>null</jk>.
301    * @return This object.
302    */
303   public Swagger addResponse(String name, ResponseInfo response) {
304      assertArgNotNull("name", name);
305      assertArgNotNull("response", response);
306      responses.put(name, response);
307      return this;
308   }
309
310   /**
311    * Bean property appender:  <property>schemes</property>.
312    *
313    * <p>
314    * The transfer protocol of the API.
315    *
316    * @param values
317    *    The values to add to this property.
318    *    <br>Valid values:
319    *    <ul>
320    *       <li><js>"http"</js>
321    *       <li><js>"https"</js>
322    *       <li><js>"ws"</js>
323    *       <li><js>"wss"</js>
324    *    </ul>
325    *    <br>Ignored if <jk>null</jk>.
326    * @return This object.
327    */
328   public Swagger addSchemes(Collection<String> values) {
329      if (nn(values))
330         schemes.addAll(values);
331      return this;
332   }
333
334   /**
335    * Bean property appender:  <property>schemes</property>.
336    *
337    * <p>
338    * The transfer protocol of the API.
339    *
340    * @param values
341    *    The values to add to this property.
342    *    <br>Valid values:
343    *    <ul>
344    *       <li><js>"http"</js>
345    *       <li><js>"https"</js>
346    *       <li><js>"ws"</js>
347    *       <li><js>"wss"</js>
348    *    </ul>
349    *    <br>Ignored if <jk>null</jk>.
350    * @return This object.
351    */
352   public Swagger addSchemes(String...values) {
353      if (nn(values))
354         for (var v : values)
355            if (nn(v))
356               schemes.add(v);
357      return this;
358   }
359
360   /**
361    * Bean property fluent setter:  <property>security</property>.
362    *
363    * <p>
364    * A declaration of which security schemes are applied for the API as a whole.
365    *
366    * @param values
367    *    The values to add to this property.
368    *    <br>Ignored if <jk>null</jk>.
369    * @return This object.
370    */
371   public Swagger addSecurity(Collection<Map<String,List<String>>> values) {
372      if (nn(values))
373         security.addAll(values);
374      return this;
375   }
376
377   /**
378    * Bean property appender:  <property>security</property>.
379    *
380    * <p>
381    * Adds a single value to the <property>securityDefinitions</property> property.
382    *
383    * @param scheme The security scheme that applies to this operation  Must not be <jk>null</jk>.
384    * @param alternatives
385    *    The list of values describes alternative security schemes that can be used (that is, there is a logical OR between the security requirements).
386    * @return This object.
387    */
388   public Swagger addSecurity(String scheme, String...alternatives) {
389      assertArgNotNull("scheme", scheme);
390      Map<String,List<String>> m = map();
391      m.put(scheme, l(alternatives));
392      security.add(m);
393      return this;
394   }
395
396   /**
397    * Bean property appender:  <property>securityDefinitions</property>.
398    *
399    * <p>
400    * Adds a single value to the <property>securityDefinitions</property> property.
401    *
402    * @param name A security name.  Must not be <jk>null</jk>.
403    * @param securityScheme A security schema.  Must not be <jk>null</jk>.
404    * @return This object.
405    */
406   public Swagger addSecurityDefinition(String name, SecurityScheme securityScheme) {
407      assertArgNotNull("name", name);
408      assertArgNotNull("securityScheme", securityScheme);
409      securityDefinitions.put(name, securityScheme);
410      return this;
411   }
412
413   /**
414    * Bean property appender:  <property>tags</property>.
415    *
416    * <p>
417    * A list of tags used by the specification with additional metadata.
418    *
419    * @param values
420    *    The values to add to this property.
421    *    <br>The order of the tags can be used to reflect on their order by the parsing tools.
422    *    <br>Not all tags that are used by the <a class="doclink" href="https://swagger.io/specification/v2#operationObject">Operation Object</a> must be declared.
423    *    <br>The tags that are not declared may be organized randomly or based on the tools' logic.
424    *    <br>Each tag name in the list MUST be unique.
425    *    <br>Ignored if <jk>null</jk>.
426    * @return This object.
427    */
428   public Swagger addTags(Collection<Tag> values) {
429      if (nn(values))
430         tags.addAll(values);
431      return this;
432   }
433
434   /**
435    * Bean property appender:  <property>tags</property>.
436    *
437    * <p>
438    * A list of tags used by the specification with additional metadata.
439    *
440    * @param values
441    *    The values to add to this property.
442    *    <br>The order of the tags can be used to reflect on their order by the parsing tools.
443    *    <br>Not all tags that are used by the <a class="doclink" href="https://swagger.io/specification/v2#operationObject">Operation Object</a> must be declared.
444    *    <br>The tags that are not declared may be organized randomly or based on the tools' logic.
445    *    <br>Each tag name in the list MUST be unique.
446    *    <br>Ignored if <jk>null</jk>.
447    * @return This object.
448    */
449   public Swagger addTags(Tag...values) {
450      if (nn(values))
451         for (var v : values)
452            if (nn(v))
453               tags.add(v);
454      return this;
455   }
456
457   /**
458    * A synonym of {@link #toString()}.
459    * @return This object serialized as JSON.
460    */
461   public String asJson() {
462      return toString();
463   }
464
465   /**
466    * Make a deep copy of this object.
467    *
468    * @return A deep copy of this object.
469    */
470   public Swagger copy() {
471      return new Swagger(this);
472   }
473
474   /**
475    * Resolves a <js>"$ref"</js> tags to nodes in this swagger document.
476    *
477    * @param <T> The class to convert the reference to.
478    * @param ref The ref tag value.  Must not be <jk>null</jk> or blank.
479    * @param c The class to convert the reference to.  Must not be <jk>null</jk>.
480    * @return The referenced node, or <jk>null</jk> if not found.
481    */
482   public <T> T findRef(String ref, Class<T> c) {
483      assertArgNotNullOrBlank("ref", ref);
484      assertArgNotNull("c", c);
485      if (! ref.startsWith("#/"))
486         throw rex("Unsupported reference:  ''{0}''", ref);
487      try {
488         return new ObjectRest(this).get(ref.substring(1), c);
489      } catch (Exception e) {
490         throw bex(e, c, "Reference ''{0}'' could not be converted to type ''{1}''.", ref, cn(c));
491      }
492   }
493
494   @Override /* Overridden from SwaggerElement */
495   public <T> T get(String property, Class<T> type) {
496      assertArgNotNull("property", property);
497      return switch (property) {
498         case "basePath" -> toType(getBasePath(), type);
499         case "consumes" -> toType(getConsumes(), type);
500         case "definitions" -> toType(getDefinitions(), type);
501         case "externalDocs" -> toType(getExternalDocs(), type);
502         case "host" -> toType(getHost(), type);
503         case "info" -> toType(getInfo(), type);
504         case "parameters" -> toType(getParameters(), type);
505         case "paths" -> toType(getPaths(), type);
506         case "produces" -> toType(getProduces(), type);
507         case "responses" -> toType(getResponses(), type);
508         case "schemes" -> toType(getSchemes(), type);
509         case "security" -> toType(getSecurity(), type);
510         case "securityDefinitions" -> toType(getSecurityDefinitions(), type);
511         case "swagger" -> toType(getSwagger(), type);
512         case "tags" -> toType(getTags(), type);
513         default -> super.get(property, type);
514      };
515   }
516
517   /**
518    * Bean property getter:  <property>basePath</property>.
519    *
520    * <p>
521    * The base path on which the API is served, which is relative to the <c>host</c>.
522    *
523    * @return The property value, or <jk>null</jk> if it is not set.
524    */
525   public String getBasePath() { return basePath; }
526
527   /**
528    * Bean property getter:  <property>consumes</property>.
529    *
530    * <p>
531    * A list of MIME types the APIs can consume.
532    *
533    * @return The property value, or <jk>null</jk> if it is not set.
534    */
535   public Set<MediaType> getConsumes() { return nullIfEmpty(consumes); }
536
537   /**
538    * Bean property getter:  <property>definitions</property>.
539    *
540    * <p>
541    * An object to hold data types produced and consumed by operations.
542    *
543    * @return The property value, or <jk>null</jk> if it is not set.
544    */
545   public Map<String,JsonMap> getDefinitions() { return nullIfEmpty(definitions); }
546
547   /**
548    * Bean property getter:  <property>externalDocs</property>.
549    *
550    * <p>
551    * Additional external documentation.
552    *
553    * @return The property value, or <jk>null</jk> if it is not set.
554    */
555   public ExternalDocumentation getExternalDocs() { return externalDocs; }
556
557   /**
558    * Bean property getter:  <property>host</property>.
559    *
560    * <p>
561    * The host (name or IP) serving the API.
562    *
563    * @return The property value, or <jk>null</jk> if it is not set.
564    */
565   public String getHost() { return host; }
566
567   /**
568    * Bean property getter:  <property>info</property>.
569    *
570    * <p>
571    * Provides metadata about the API.
572    *
573    * @return The property value, or <jk>null</jk> if it is not set.
574    */
575   public Info getInfo() { return info; }
576
577   /**
578    * Shortcut for calling <c>getPaths().get(path).get(operation);</c>
579    *
580    * @param path The path (e.g. <js>"/foo"</js>).  Must not be <jk>null</jk>.
581    * @param operation The HTTP operation (e.g. <js>"get"</js>).  Must not be <jk>null</jk>.
582    * @return The operation for the specified path and operation id, or <jk>null</jk> if it doesn't exist.
583    */
584   public Operation getOperation(String path, String operation) {
585      assertArgNotNull("path", path);
586      assertArgNotNull("operation", operation);
587      return opt(getPath(path)).map(x -> x.get(operation)).orElse(null);
588   }
589
590   /**
591    * Convenience method for calling <c>getPath(path).get(method).getParameter(in,name);</c>
592    *
593    * @param path The HTTP path.  Must not be <jk>null</jk>.
594    * @param method The HTTP method.  Must not be <jk>null</jk>.
595    * @param in The parameter type.  Must not be <jk>null</jk>.
596    * @param name The parameter name.  Can be <jk>null</jk> for parameter type <c>body</c>.
597    * @return The parameter information or <jk>null</jk> if not found.
598    */
599   public ParameterInfo getParameterInfo(String path, String method, String in, String name) {
600      assertArgNotNull("path", path);
601      assertArgNotNull("method", method);
602      assertArgNotNull("in", in);
603      return opt(getPath(path)).map(x -> x.get(method)).map(x -> x.getParameter(in, name)).orElse(null);
604   }
605
606   /**
607    * Bean property getter:  <property>parameters</property>.
608    *
609    * <p>
610    * An object to hold parameters that can be used across operations.
611    *
612    * @return The property value, or <jk>null</jk> if it is not set.
613    */
614   public Map<String,ParameterInfo> getParameters() { return nullIfEmpty(parameters); }
615
616   /**
617    * Shortcut for calling <c>getPaths().get(path);</c>
618    *
619    * @param path The path (e.g. <js>"/foo"</js>).  Must not be <jk>null</jk>.
620    * @return The operation map for the specified path, or <jk>null</jk> if it doesn't exist.
621    */
622   public OperationMap getPath(String path) {
623      assertArgNotNull("path", path);
624      return opt(getPaths()).map(x -> x.get(path)).orElse(null);
625   }
626
627   /**
628    * Bean property getter:  <property>paths</property>.
629    *
630    * <p>
631    * The available paths and operations for the API.
632    *
633    * @return The property value, or <jk>null</jk> if it is not set.
634    */
635   public Map<String,OperationMap> getPaths() { return nullIfEmpty(paths); }
636
637   /**
638    * Bean property getter:  <property>produces</property>.
639    *
640    * <p>
641    * A list of MIME types the APIs can produce.
642    *
643    * @return The property value, or <jk>null</jk> if it is not set.
644    */
645   public Set<MediaType> getProduces() { return nullIfEmpty(produces); }
646
647   /**
648    * Shortcut for calling <c>getPaths().get(path).get(operation).getResponse(status);</c>
649    *
650    * @param path The path (e.g. <js>"/foo"</js>).
651    * @param operation The HTTP operation (e.g. <js>"get"</js>).
652    * @param status The HTTP response status (e.g. <js>"200"</js>).
653    * @return The operation for the specified path and operation id, or <jk>null</jk> if it doesn't exist.
654    */
655   public ResponseInfo getResponseInfo(String path, String operation, int status) {
656      return getResponseInfo(path, operation, String.valueOf(status));
657   }
658
659   /**
660    * Shortcut for calling <c>getPaths().get(path).get(operation).getResponse(status);</c>
661    *
662    * @param path The path (e.g. <js>"/foo"</js>).  Must not be <jk>null</jk>.
663    * @param operation The HTTP operation (e.g. <js>"get"</js>).  Must not be <jk>null</jk>.
664    * @param status The HTTP response status (e.g. <js>"200"</js>).  Must not be <jk>null</jk>.
665    * @return The operation for the specified path and operation id, or <jk>null</jk> if it doesn't exist.
666    */
667   public ResponseInfo getResponseInfo(String path, String operation, String status) {
668      assertArgNotNull("path", path);
669      assertArgNotNull("operation", operation);
670      assertArgNotNull("status", status);
671      return opt(getPath(path)).map(x -> x.get(operation)).map(x -> x.getResponse(status)).orElse(null);
672   }
673
674   /**
675    * Bean property getter:  <property>responses</property>.
676    *
677    * <p>
678    * An object to hold responses that can be used across operations.
679    *
680    * @return The property value, or <jk>null</jk> if it is not set.
681    */
682   public Map<String,ResponseInfo> getResponses() { return nullIfEmpty(responses); }
683
684   /**
685    * Bean property getter:  <property>schemes</property>.
686    *
687    * <p>
688    * The transfer protocol of the API.
689    *
690    * @return The property value, or <jk>null</jk> if it is not set.
691    */
692   public Set<String> getSchemes() { return nullIfEmpty(schemes); }
693
694   /**
695    * Bean property getter:  <property>security</property>.
696    *
697    * <p>
698    * A declaration of which security schemes are applied for the API as a whole.
699    *
700    * @return The property value, or <jk>null</jk> if it is not set.
701    */
702   public List<Map<String,List<String>>> getSecurity() { return nullIfEmpty(security); }
703
704   /**
705    * Bean property getter:  <property>securityDefinitions</property>.
706    *
707    * <p>
708    * Security scheme definitions that can be used across the specification.
709    *
710    * @return The property value, or <jk>null</jk> if it is not set.
711    */
712   public Map<String,SecurityScheme> getSecurityDefinitions() { return nullIfEmpty(securityDefinitions); }
713
714   /**
715    * Bean property getter:  <property>swagger</property>.
716    *
717    * <p>
718    * Specifies the Swagger Specification version being used.
719    *
720    * @return The property value, or <jk>null</jk> if it is not set.
721    */
722   public String getSwagger() { return swagger; }
723
724   /**
725    * Bean property getter:  <property>tags</property>.
726    *
727    * <p>
728    * A list of tags used by the specification with additional metadata.
729    *
730    * @return The property value, or <jk>null</jk> if it is not set.
731    */
732   public Set<Tag> getTags() { return nullIfEmpty(tags); }
733
734   @Override /* Overridden from SwaggerElement */
735   public Set<String> keySet() {
736      // @formatter:off
737      var s = setb(String.class)
738         .addIf(nn(basePath), "basePath")
739         .addIf(ne(consumes), "consumes")
740         .addIf(ne(definitions), "definitions")
741         .addIf(nn(externalDocs), "externalDocs")
742         .addIf(nn(host), "host")
743         .addIf(nn(info), "info")
744         .addIf(ne(parameters), "parameters")
745         .addIf(ne(paths), "paths")
746         .addIf(ne(produces), "produces")
747         .addIf(ne(responses), "responses")
748         .addIf(ne(schemes), "schemes")
749         .addIf(ne(security), "security")
750         .addIf(ne(securityDefinitions), "securityDefinitions")
751         .addIf(nn(swagger), "swagger")
752         .addIf(ne(tags), "tags")
753         .build();
754      // @formatter:on
755      return new MultiSet<>(s, super.keySet());
756   }
757
758   @SuppressWarnings({ "rawtypes", "unchecked" })
759   @Override /* Overridden from SwaggerElement */
760   public Swagger set(String property, Object value) {
761      assertArgNotNull("property", property);
762      return switch (property) {
763         case "basePath" -> setBasePath(s(value));
764         case "consumes" -> setConsumes(toListBuilder(value, MediaType.class).sparse().build());
765         case "definitions" -> setDefinitions(toMapBuilder(value, String.class, JsonMap.class).sparse().build());
766         case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class));
767         case "host" -> setHost(s(value));
768         case "info" -> setInfo(toType(value, Info.class));
769         case "parameters" -> setParameters(toMapBuilder(value, String.class, ParameterInfo.class).sparse().build());
770         case "paths" -> setPaths(toMapBuilder(value, String.class, OperationMap.class).sparse().build());
771         case "produces" -> setProduces(toListBuilder(value, MediaType.class).sparse().build());
772         case "responses" -> setResponses(toMapBuilder(value, String.class, ResponseInfo.class).sparse().build());
773         case "schemes" -> setSchemes(toListBuilder(value, String.class).sparse().build());
774         case "security" -> setSecurity((List)toListBuilder(value, MapOfStringLists.class).sparse().build());
775         case "securityDefinitions" -> setSecurityDefinitions(toMapBuilder(value, String.class, SecurityScheme.class).sparse().build());
776         case "swagger" -> setSwagger(s(value));
777         case "tags" -> setTags(toListBuilder(value, Tag.class).sparse().build());
778         default -> {
779            super.set(property, value);
780            yield this;
781         }
782      };
783   }
784
785   /**
786    * Bean property setter:  <property>basePath</property>.
787    *
788    * <p>
789    * The base path on which the API is served, which is relative to the <c>host</c>.
790    *
791    * @param value
792    *    The new value for this property.
793    *    <br>If it is not included, the API is served directly under the <c>host</c>.
794    *    <br>The value MUST start with a leading slash (/).
795    *    <br>The <c>basePath</c> does not support <a class="doclink" href="https://swagger.io/specification/v2#pathTemplating">path templating</a>.
796    *    <br>Can be <jk>null</jk> to unset the property.
797    * @return This object.
798    */
799   public Swagger setBasePath(String value) {
800      basePath = value;
801      return this;
802   }
803
804   /**
805    * Bean property setter:  <property>consumes</property>.
806    *
807    * <p>
808    * A list of MIME types the APIs can consume.
809    *
810    * @param value
811    *    The new value for this property.
812    *    <br>Value MUST be as described under <a class="doclink" href="https://swagger.io/specification#mimeTypes">Swagger Mime Types</a>.
813    *    <br>Can be <jk>null</jk> to unset the property.
814    * @return This object.
815    */
816   public Swagger setConsumes(Collection<MediaType> value) {
817      consumes.clear();
818      if (nn(value))
819         consumes.addAll(value);
820      return this;
821   }
822
823   /**
824    * Bean property fluent setter:  <property>consumes</property>.
825    *
826    * <p>
827    * A list of MIME types the APIs can consume.
828    *
829    * @param value
830    *    The values to set on this property.
831    * @return This object.
832    */
833   public Swagger setConsumes(MediaType...value) {
834      setConsumes(setb(MediaType.class).sparse().add(value).build());
835      return this;
836   }
837
838   /**
839    * Bean property setter:  <property>definitions</property>.
840    *
841    * <p>
842    * An object to hold data types produced and consumed by operations.
843    *
844    * @param value
845    *    The new value for this property.
846    *    <br>Can be <jk>null</jk> to unset the property.
847    * @return This object.
848    */
849   public Swagger setDefinitions(Map<String,JsonMap> value) {
850      definitions.clear();
851      if (nn(value))
852         definitions.putAll(value);
853      return this;
854   }
855
856   /**
857    * Bean property setter:  <property>externalDocs</property>.
858    *
859    * <p>
860    * Additional external documentation.
861    *
862    * @param value
863    *    The new value for this property.
864    *    <br>Can be <jk>null</jk> to unset the property.
865    * @return This object.
866    */
867   public Swagger setExternalDocs(ExternalDocumentation value) {
868      externalDocs = value;
869      return this;
870   }
871
872   /**
873    * Bean property setter:  <property>host</property>.
874    *
875    * <p>
876    * The host (name or IP) serving the API.
877    *
878    * @param value
879    *    The new value for this property.
880    *    <br>This MUST be the host only and does not include the scheme nor sub-paths.
881    *    <br>It MAY include a port.
882    *    <br>If the host is not included, the host serving the documentation is to be used (including the port).
883    *    <br>The host does not support <a class="doclink" href="https://swagger.io/specification/v2#pathTemplating">path templating</a>
884    *    <br>Can be <jk>null</jk> to unset the property.
885    * @return This object.
886    */
887   public Swagger setHost(String value) {
888      host = value;
889      return this;
890   }
891
892   /**
893    * Bean property setter:  <property>info</property>.
894    *
895    * <p>
896    * Provides metadata about the API.
897    *
898    * @param value
899    *    The new value for this property.
900    *    <br>Property value is required.
901    *    <br>Can be <jk>null</jk> to unset the property.
902    * @return This object.
903    */
904   public Swagger setInfo(Info value) {
905      info = value;
906      return this;
907   }
908
909   /**
910    * Bean property setter:  <property>parameters</property>.
911    *
912    * <p>
913    * An object to hold parameters that can be used across operations.
914    *
915    * @param value
916    *    The new value for this property.
917    *    <br>Can be <jk>null</jk> to unset the property.
918    * @return This object.
919    */
920   public Swagger setParameters(Map<String,ParameterInfo> value) {
921      parameters.clear();
922      if (nn(value))
923         parameters.putAll(value);
924      return this;
925   }
926
927   /**
928    * Bean property setter:  <property>paths</property>.
929    *
930    * <p>
931    * The available paths and operations for the API.
932    *
933    * @param value
934    *    The new value for this property.
935    *    <br>Property value is required.
936    *    <br>Can be <jk>null</jk> to unset the property.
937    * @return This object.
938    */
939   public Swagger setPaths(Map<String,OperationMap> value) {
940      paths.clear();
941      if (nn(value))
942         paths.putAll(value);
943      return this;
944   }
945
946   /**
947    * Bean property setter:  <property>produces</property>.
948    *
949    * <p>
950    * A list of MIME types the APIs can produce.
951    *
952    * @param value
953    *    The new value for this property.
954    *    <br>Value MUST be as described under <a class="doclink" href="https://swagger.io/specification#mimeTypes">Swagger Mime Types</a>.
955    *    <br>Can be <jk>null</jk> to unset the property.
956    * @return This object.
957    */
958   public Swagger setProduces(Collection<MediaType> value) {
959      produces.clear();
960      if (nn(value))
961         produces.addAll(value);
962      return this;
963   }
964
965   /**
966    * Bean property fluent setter:  <property>produces</property>.
967    *
968    * <p>
969    * A list of MIME types the APIs can produce.
970    *
971    * @param value
972    *    The new value for this property.
973    * @return This object.
974    */
975   public Swagger setProduces(MediaType...value) {
976      setProduces(setb(MediaType.class).sparse().add(value).build());
977      return this;
978   }
979
980   /**
981    * Bean property setter:  <property>responses</property>.
982    *
983    * <p>
984    * An object to hold responses that can be used across operations.
985    *
986    * @param value
987    *    The new value for this property.
988    *    <br>Can be <jk>null</jk> to unset the property.
989    * @return This object.
990    */
991   public Swagger setResponses(Map<String,ResponseInfo> value) {
992      responses.clear();
993      if (nn(value))
994         responses.putAll(value);
995      return this;
996   }
997
998   /**
999    * Bean property setter:  <property>schemes</property>.
1000    *
1001    * <p>
1002    * The transfer protocol of the API.
1003    *
1004    * @param value
1005    *    The new value for this property.
1006    *    <br>Valid values:
1007    *    <ul>
1008    *       <li><js>"http"</js>
1009    *       <li><js>"https"</js>
1010    *       <li><js>"ws"</js>
1011    *       <li><js>"wss"</js>
1012    *    </ul>
1013    *    <br>Can be <jk>null</jk> to unset the property.
1014    * @return This object.
1015    */
1016   public Swagger setSchemes(Collection<String> value) {
1017      schemes.clear();
1018      if (nn(value))
1019         schemes.addAll(value);
1020      return this;
1021   }
1022
1023   /**
1024    * Bean property fluent setter:  <property>schemes</property>.
1025    *
1026    * <p>
1027    * The transfer protocol of the API.
1028    *
1029    * @param value
1030    *    The new value for this property.
1031    *    <br>Strings can be JSON arrays.
1032    * @return This object.
1033    */
1034   public Swagger setSchemes(String...value) {
1035      setSchemes(setb(String.class).sparse().addJson(value).build());
1036      return this;
1037   }
1038
1039   /**
1040    * Bean property setter:  <property>security</property>.
1041    *
1042    * <p>
1043    * A declaration of which security schemes are applied for the API as a whole.
1044    *
1045    * @param value
1046    *    The new value for this property.
1047    *    <br>Can be <jk>null</jk> to unset the property.
1048    * @return This object.
1049    */
1050   public Swagger setSecurity(Collection<Map<String,List<String>>> value) {
1051      security.clear();
1052      if (nn(value))
1053         security.addAll(value);
1054      return this;
1055   }
1056
1057   /**
1058    * Bean property setter:  <property>securityDefinitions</property>.
1059    *
1060    * <p>
1061    * Security scheme definitions that can be used across the specification.
1062    *
1063    * @param value
1064    *    The new value for this property.
1065    *    <br>Can be <jk>null</jk> to unset the property.
1066    * @return This object.
1067    */
1068   public Swagger setSecurityDefinitions(Map<String,SecurityScheme> value) {
1069      securityDefinitions.clear();
1070      if (nn(value))
1071         securityDefinitions.putAll(value);
1072      return this;
1073   }
1074
1075   /**
1076    * Bean property setter:  <property>swagger</property>.
1077    *
1078    * <p>
1079    * Specifies the Swagger Specification version being used.
1080    *
1081    * @param value
1082    *    The new value for this property.
1083    *    <br>Property value is required.
1084    *    <br>Can be <jk>null</jk> to unset the property.
1085    * @return This object.
1086    */
1087   public Swagger setSwagger(String value) {
1088      swagger = value;
1089      return this;
1090   }
1091
1092   /**
1093    * Bean property setter:  <property>tags</property>.
1094    *
1095    * <p>
1096    * A list of tags used by the specification with additional metadata.
1097    *
1098    * @param value
1099    *    The new value for this property.
1100    *    <br>The order of the tags can be used to reflect on their order by the parsing tools.
1101    *    <br>Not all tags that are used by the <a class="doclink" href="https://swagger.io/specification/v2#operationObject">Operation Object</a> must be declared.
1102    *    <br>The tags that are not declared may be organized randomly or based on the tools' logic.
1103    *    <br>Each tag name in the list MUST be unique.
1104    *    <br>Can be <jk>null</jk> to unset the property.
1105    * @return This object.
1106    */
1107   public Swagger setTags(Collection<Tag> value) {
1108      tags.clear();
1109      if (nn(value))
1110         tags.addAll(value);
1111      return this;
1112   }
1113
1114   /**
1115    * Bean property setter:  <property>tags</property>.
1116    *
1117    * <p>
1118    * A list of tags used by the specification with additional metadata.
1119    *
1120    * @param value
1121    *    The new value for this property.
1122    *    <br>The order of the tags can be used to reflect on their order by the parsing tools.
1123    *    <br>Not all tags that are used by the <a class="doclink" href="https://swagger.io/specification/v2#operationObject">Operation Object</a> must be declared.
1124    *    <br>The tags that are not declared may be organized randomly or based on the tools' logic.
1125    *    <br>Each tag name in the list MUST be unique.
1126    *    <br>Ignored if <jk>null</jk>.
1127    * @return This object.
1128    */
1129   public Swagger setTags(Tag...value) {
1130      setTags(setb(Tag.class).sparse().add(value).build());
1131      return this;
1132   }
1133
1134   /**
1135    * Sets strict mode on this bean.
1136    *
1137    * @return This object.
1138    */
1139   @Override
1140   public Swagger strict() {
1141      super.strict();
1142      return this;
1143   }
1144
1145   /**
1146    * Sets strict mode on this bean.
1147    *
1148    * @param value
1149    *    The new value for this property.
1150    *    <br>Non-boolean values will be converted to boolean using <code>Boolean.<jsm>valueOf</jsm>(value.toString())</code>.
1151    *    <br>Can be <jk>null</jk> (interpreted as <jk>false</jk>).
1152    * @return This object.
1153    */
1154   @Override
1155   public Swagger strict(Object value) {
1156      super.strict(value);
1157      return this;
1158   }
1159
1160   @Override /* Overridden from Object */
1161   public String toString() {
1162      return JsonSerializer.DEFAULT.toString(this);
1163   }
1164}