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.openapi3;
018
019import static org.apache.juneau.common.utils.Utils.*;
020import static org.apache.juneau.internal.ClassUtils.*;
021import static org.apache.juneau.internal.CollectionUtils.*;
022import static org.apache.juneau.internal.ConverterUtils.*;
023
024import java.util.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.common.utils.*;
028import org.apache.juneau.internal.*;
029import org.apache.juneau.json.*;
030import org.apache.juneau.objecttools.*;
031
032/**
033 * This is the root document object for the OpenAPI specification.
034 *
035 * <p>
036 * The OpenAPI Object is the root document that describes an entire API. It contains metadata about the API,
037 * available paths and operations, parameters, responses, authentication methods, and other information.
038 *
039 * <h5 class='section'>OpenAPI Specification:</h5>
040 * <p>
041 * The OpenAPI Object is composed of the following fields:
042 * <ul class='spaced-list'>
043 *    <li><c>openapi</c> (string, REQUIRED) - The OpenAPI Specification version (e.g., "3.0.0")
044 *    <li><c>info</c> ({@link Info}, REQUIRED) - Provides metadata about the API
045 *    <li><c>servers</c> (array of {@link Server}) - An array of Server Objects providing connectivity information
046 *    <li><c>paths</c> (map of {@link PathItem}) - The available paths and operations for the API
047 *    <li><c>components</c> ({@link Components}) - An element to hold various schemas for reuse
048 *    <li><c>security</c> (array of {@link SecurityRequirement}) - Security mechanisms applied to all operations
049 *    <li><c>tags</c> (array of {@link Tag}) - A list of tags for API documentation control
050 *    <li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation
051 * </ul>
052 *
053 * <h5 class='section'>Example:</h5>
054 * <p class='bjava'>
055 *    <jc>// Create an OpenAPI document</jc>
056 *    OpenApi <jv>doc</jv> = <jk>new</jk> OpenApi()
057 *       .setOpenapi(<js>"3.0.0"</js>)
058 *       .setInfo(
059 *          <jk>new</jk> Info()
060 *             .setTitle(<js>"My API"</js>)
061 *             .setVersion(<js>"1.0.0"</js>)
062 *       )
063 *       .setPaths(
064 *          JsonMap.<jsm>of</jsm>(
065 *             <js>"/pets"</js>, <jk>new</jk> PathItem()
066 *                .setGet(<jk>new</jk> Operation()
067 *                   .setSummary(<js>"List all pets"</js>)
068 *                )
069 *          )
070 *       );
071 * </p>
072 *
073 * <h5 class='section'>See Also:</h5><ul>
074 *    <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#openapi-object">OpenAPI Specification &gt; OpenAPI Object</a>
075 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/basic-structure/">OpenAPI Basic Structure</a>
076 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
077 * </ul>
078 */
079public class OpenApi extends OpenApiElement {
080
081   /** Represents a null OpenAPI document */
082   public static final OpenApi NULL = new OpenApi();
083
084   private static final Comparator<String> PATH_COMPARATOR = (o1, o2) -> o1.replace('{', '@').compareTo(o2.replace('{', '@'));
085
086   private String openapi = "3.0.0";
087   private Info info;
088   private List<Server> servers;
089   private Map<String,PathItem> paths;
090   private Components components;
091   private List<SecurityRequirement> security;
092   private List<Tag> tags;
093   private ExternalDocumentation externalDocs;
094
095   /**
096    * Default constructor.
097    */
098   public OpenApi() {}
099
100   /**
101    * Copy constructor.
102    *
103    * @param copyFrom The object to copy.
104    */
105   public OpenApi(OpenApi copyFrom) {
106      super(copyFrom);
107      this.openapi = copyFrom.openapi;
108      this.info = copyFrom.info;
109      this.servers = copyOf(copyFrom.servers);
110      this.paths = copyOf(copyFrom.paths);
111      this.components = copyFrom.components;
112      this.security = copyOf(copyFrom.security);
113      this.tags = copyOf(copyFrom.tags);
114      this.externalDocs = copyFrom.externalDocs;
115   }
116
117   /**
118    * Make a deep copy of this object.
119    *
120    * @return A deep copy of this object.
121    */
122   public OpenApi copy() {
123      return new OpenApi(this);
124   }
125
126   /**
127    * Returns the OpenAPI version.
128    *
129    * @return The OpenAPI version.
130    */
131   public String getOpenapi() {
132      return openapi;
133   }
134
135   /**
136    * Sets the OpenAPI version.
137    *
138    * @param value The new value for this property.
139    * @return This object.
140    */
141   public OpenApi setOpenapi(String value) {
142      this.openapi = value;
143      return this;
144   }
145
146   /**
147    * Returns the info object.
148    *
149    * @return The info object.
150    */
151   public Info getInfo() {
152      return info;
153   }
154
155   /**
156    * Sets the info object.
157    *
158    * @param value The new value for this property.
159    * @return This object.
160    */
161   public OpenApi setInfo(Info value) {
162      this.info = value;
163      return this;
164   }
165
166   /**
167    * Returns the servers list.
168    *
169    * @return The servers list.
170    */
171   public List<Server> getServers() {
172      return servers;
173   }
174
175   /**
176    * Sets the servers list.
177    *
178    * @param value The new value for this property.
179    * @return This object.
180    */
181   public OpenApi setServers(List<Server> value) {
182      this.servers = value;
183      return this;
184   }
185
186   /**
187    * Returns the paths map.
188    *
189    * @return The paths map.
190    */
191   public Map<String,PathItem> getPaths() {
192      return paths;
193   }
194
195   /**
196    * Sets the paths map.
197    *
198    * @param value The new value for this property.
199    * @return This object.
200    */
201   public OpenApi setPaths(Map<String,PathItem> value) {
202      this.paths = mapBuilder(String.class,PathItem.class).sparse().sorted(PATH_COMPARATOR).addAll(value).build();
203      return this;
204   }
205
206   /**
207    * Adds a path to this OpenAPI document.
208    *
209    * @param path The path string.  Must not be <jk>null</jk>.
210    * @param pathItem The path item.  Must not be <jk>null</jk>.
211    * @return This object.
212    */
213   public OpenApi addPath(String path, PathItem pathItem) {
214      assertArgNotNull("path", path);
215      assertArgNotNull("pathItem", pathItem);
216      if (paths == null)
217         paths = new TreeMap<>(PATH_COMPARATOR);
218      getPaths().put(path, pathItem);
219      return this;
220   }
221
222   /**
223    * Returns the components object.
224    *
225    * @return The components object.
226    */
227   public Components getComponents() {
228      return components;
229   }
230
231   /**
232    * Sets the components object.
233    *
234    * @param value The new value for this property.
235    * @return This object.
236    */
237   public OpenApi setComponents(Components value) {
238      this.components = value;
239      return this;
240   }
241
242   /**
243    * Returns the security requirements list.
244    *
245    * @return The security requirements list.
246    */
247   public List<SecurityRequirement> getSecurity() {
248      return security;
249   }
250
251   /**
252    * Sets the security requirements list.
253    *
254    * @param value The new value for this property.
255    * @return This object.
256    */
257   public OpenApi setSecurity(List<SecurityRequirement> value) {
258      this.security = value;
259      return this;
260   }
261
262   /**
263    * Returns the tags list.
264    *
265    * @return The tags list.
266    */
267   public List<Tag> getTags() {
268      return tags;
269   }
270
271   /**
272    * Sets the tags list.
273    *
274    * @param value The new value for this property.
275    * @return This object.
276    */
277   public OpenApi setTags(List<Tag> value) {
278      this.tags = value;
279      return this;
280   }
281
282   /**
283    * Bean property setter:  <property>tags</property>.
284    *
285    * <p>
286    * A list of tags used by the specification with additional metadata.
287    *
288    * @param value
289    *    The new value for this property.
290    *    <br>Ignored if <jk>null</jk>.
291    * @return This object.
292    */
293   public OpenApi setTags(Tag...value) {
294      setTags(listBuilder(Tag.class).sparse().add(value).build());
295      return this;
296   }
297
298   /**
299    * Bean property appender:  <property>tags</property>.
300    *
301    * <p>
302    * A list of tags used by the specification with additional metadata.
303    *
304    * @param values
305    *    The values to add to this property.
306    *    <br>Ignored if <jk>null</jk>.
307    * @return This object.
308    */
309   public OpenApi addTags(Tag...values) {
310      tags = listBuilder(tags).sparse().add(values).build();
311      return this;
312   }
313
314   /**
315    * Bean property appender:  <property>tags</property>.
316    *
317    * <p>
318    * A list of tags used by the specification with additional metadata.
319    *
320    * @param values
321    *    The values to add to this property.
322    *    <br>Ignored if <jk>null</jk>.
323    * @return This object.
324    */
325   public OpenApi addTags(Collection<Tag> values) {
326      tags = listBuilder(tags).sparse().addAll(values).build();
327      return this;
328   }
329
330   /**
331    * Bean property fluent setter:  <property>servers</property>.
332    *
333    * <p>
334    * An array of Server Objects, which provide connectivity information to a target server.
335    *
336    * @param values
337    *    The values to add to this property.
338    *    <br>Ignored if <jk>null</jk>.
339    * @return This object.
340    */
341   public OpenApi addServers(Server...values) {
342      servers = listBuilder(servers).sparse().add(values).build();
343      return this;
344   }
345
346   /**
347    * Bean property fluent setter:  <property>servers</property>.
348    *
349    * <p>
350    * An array of Server Objects, which provide connectivity information to a target server.
351    *
352    * @param values
353    *    The values to add to this property.
354    *    <br>Ignored if <jk>null</jk>.
355    * @return This object.
356    */
357   public OpenApi addServers(Collection<Server> values) {
358      servers = listBuilder(servers).sparse().addAll(values).build();
359      return this;
360   }
361
362   /**
363    * Bean property fluent setter:  <property>security</property>.
364    *
365    * <p>
366    * A declaration of which security mechanisms can be used across the API.
367    *
368    * @param values
369    *    The values to add to this property.
370    *    <br>Ignored if <jk>null</jk>.
371    * @return This object.
372    */
373   public OpenApi addSecurity(SecurityRequirement...values) {
374      security = listBuilder(security).sparse().add(values).build();
375      return this;
376   }
377
378   /**
379    * Bean property fluent setter:  <property>security</property>.
380    *
381    * <p>
382    * A declaration of which security mechanisms can be used across the API.
383    *
384    * @param values
385    *    The values to add to this property.
386    *    <br>Ignored if <jk>null</jk>.
387    * @return This object.
388    */
389   public OpenApi addSecurity(Collection<SecurityRequirement> values) {
390      security = listBuilder(security).sparse().addAll(values).build();
391      return this;
392   }
393
394   /**
395    * Returns the external documentation.
396    *
397    * @return The external documentation.
398    */
399   public ExternalDocumentation getExternalDocs() {
400      return externalDocs;
401   }
402
403   /**
404    * Sets the external documentation.
405    *
406    * @param value The new value for this property.
407    * @return This object.
408    */
409   public OpenApi setExternalDocs(ExternalDocumentation value) {
410      this.externalDocs = value;
411      return this;
412   }
413
414   /**
415    * Finds a reference within this OpenAPI document.
416    *
417    * @param ref The reference string (e.g., <js>"#/components/schemas/User"</js>).  Must not be <jk>null</jk> or blank.
418    * @param c The expected class type.  Must not be <jk>null</jk>.
419    * @return The referenced node, or <jk>null</jk> if not found.
420    */
421   public <T> T findRef(String ref, Class<T> c) {
422      assertArgNotNullOrBlank("ref", ref);
423      assertArgNotNull("c", c);
424      if (! ref.startsWith("#/"))
425         throw new BasicRuntimeException("Unsupported reference:  ''{0}''", ref);
426      try {
427         return new ObjectRest(this).get(ref.substring(1), c);
428      } catch (Exception e) {
429         throw new BeanRuntimeException(e, c, "Reference ''{0}'' could not be converted to type ''{1}''.", ref, className(c));
430      }
431   }
432
433   @Override
434   public String toString() {
435      return JsonSerializer.DEFAULT.toString(this);
436   }
437
438   @Override /* Overridden from OpenApiElement */
439   public <T> T get(String property, Class<T> type) {
440      assertArgNotNull("property", property);
441      return switch (property) {
442         case "openapi" -> toType(getOpenapi(), type);
443         case "info" -> toType(getInfo(), type);
444         case "servers" -> toType(getServers(), type);
445         case "paths" -> toType(getPaths(), type);
446         case "components" -> toType(getComponents(), type);
447         case "security" -> toType(getSecurity(), type);
448         case "tags" -> toType(getTags(), type);
449         case "externalDocs" -> toType(getExternalDocs(), type);
450         default -> super.get(property, type);
451      };
452   }
453
454   @Override /* Overridden from OpenApiElement */
455   public OpenApi set(String property, Object value) {
456      assertArgNotNull("property", property);
457      return switch (property) {
458         case "components" -> setComponents(toType(value, Components.class));
459         case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class));
460         case "info" -> setInfo(toType(value, Info.class));
461         case "openapi" -> setOpenapi(Utils.s(value));
462         case "paths" -> setPaths(mapBuilder(String.class, PathItem.class).sparse().addAny(value).build());
463         case "security" -> setSecurity(listBuilder(SecurityRequirement.class).sparse().addAny(value).build());
464         case "servers" -> setServers(listBuilder(Server.class).sparse().addAny(value).build());
465         case "tags" -> setTags(listBuilder(Tag.class).sparse().addAny(value).build());
466         default -> {
467            super.set(property, value);
468            yield this;
469         }
470      };
471   }
472
473   @Override /* Overridden from OpenApiElement */
474   public Set<String> keySet() {
475      var s = setBuilder(String.class)
476         .addIf(components != null, "components")
477         .addIf(externalDocs != null, "externalDocs")
478         .addIf(info != null, "info")
479         .addIf(openapi != null, "openapi")
480         .addIf(paths != null, "paths")
481         .addIf(security != null, "security")
482         .addIf(servers != null, "servers")
483         .addIf(tags != null, "tags")
484         .build();
485      return new MultiSet<>(s, super.keySet());
486   }
487
488   @Override /* Overridden from OpenApiElement */
489   public OpenApi strict() {
490      super.strict();
491      return this;
492   }
493
494   @Override /* Overridden from OpenApiElement */
495   public OpenApi strict(Object value) {
496      super.strict(value);
497      return this;
498   }
499
500}