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