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