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.Utils.*; 022import static org.apache.juneau.internal.ConverterUtils.*; 023 024import java.util.*; 025 026import org.apache.juneau.commons.collections.*; 027 028/** 029 * Describes a single API operation on a path. 030 * 031 * <p> 032 * The Operation Object describes a single operation (such as GET, POST, PUT, DELETE) that can be performed on a path. 033 * Operations are the core of the API specification, defining what actions can be taken, what parameters they accept, 034 * and what responses they return. 035 * 036 * <h5 class='section'>OpenAPI Specification:</h5> 037 * <p> 038 * The Operation Object is composed of the following fields: 039 * <ul class='spaced-list'> 040 * <li><c>tags</c> (array of string) - A list of tags for API documentation control 041 * <li><c>summary</c> (string) - A short summary of what the operation does 042 * <li><c>description</c> (string) - A verbose explanation of the operation behavior (CommonMark syntax may be used) 043 * <li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation for this operation 044 * <li><c>operationId</c> (string) - Unique string used to identify the operation 045 * <li><c>parameters</c> (array of {@link Parameter}) - A list of parameters that are applicable for this operation 046 * <li><c>requestBody</c> ({@link RequestBodyInfo}) - The request body applicable for this operation 047 * <li><c>responses</c> (map of {@link Response}, REQUIRED) - The list of possible responses as they are returned from executing this operation 048 * <li><c>callbacks</c> (map of {@link Callback}) - A map of possible out-of band callbacks related to the parent operation 049 * <li><c>deprecated</c> (boolean) - Declares this operation to be deprecated 050 * <li><c>security</c> (array of {@link SecurityRequirement}) - A declaration of which security mechanisms can be used for this operation 051 * <li><c>servers</c> (array of {@link Server}) - An alternative server array to service this operation 052 * </ul> 053 * 054 * <h5 class='section'>Example:</h5> 055 * <p class='bjava'> 056 * <jc>// Create an Operation object for a GET endpoint</jc> 057 * Operation <jv>operation</jv> = <jk>new</jk> Operation() 058 * .setTags(<js>"pet"</js>) 059 * .setSummary(<js>"Find pets by status"</js>) 060 * .setDescription(<js>"Multiple status values can be provided with comma separated strings"</js>) 061 * .setOperationId(<js>"findPetsByStatus"</js>) 062 * .setParameters( 063 * <jk>new</jk> Parameter() 064 * .setName(<js>"status"</js>) 065 * .setIn(<js>"query"</js>) 066 * .setDescription(<js>"Status values that need to be considered for filter"</js>) 067 * .setRequired(<jk>true</jk>) 068 * .setSchema( 069 * <jk>new</jk> SchemaInfo().setType(<js>"string"</js>) 070 * ) 071 * ) 072 * .setResponses( 073 * JsonMap.<jsm>of</jsm>( 074 * <js>"200"</js>, <jk>new</jk> Response() 075 * .setDescription(<js>"successful operation"</js>) 076 * .setContent( 077 * JsonMap.<jsm>of</jsm>( 078 * <js>"application/json"</js>, <jk>new</jk> MediaType() 079 * .setSchema(<jk>new</jk> SchemaInfo().<jsm>setType</jsm>(<js>"array"</js>)) 080 * ) 081 * ) 082 * ) 083 * ); 084 * </p> 085 * 086 * <h5 class='section'>See Also:</h5><ul> 087 * <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#operation-object">OpenAPI Specification > Operation Object</a> 088 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/paths-and-operations/">OpenAPI Paths and Operations</a> 089 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a> 090 * </ul> 091 */ 092public class Operation extends OpenApiElement { 093 094 private List<String> tags = list(); 095 private String summary, description, operationId; 096 private ExternalDocumentation externalDocs; 097 private List<Parameter> parameters = list(); 098 private RequestBodyInfo requestBody; 099 private Map<String,Response> responses = map(); 100 private Map<String,Callback> callbacks = map(); 101 private Boolean deprecated; 102 private List<SecurityRequirement> security = list(); 103 private List<Server> servers = list(); 104 105 /** 106 * Default constructor. 107 */ 108 public Operation() {} 109 110 /** 111 * Copy constructor. 112 * 113 * @param copyFrom The object to copy. 114 */ 115 public Operation(Operation copyFrom) { 116 super(copyFrom); 117 if (nn(copyFrom.tags)) 118 tags.addAll(copyFrom.tags); 119 this.summary = copyFrom.summary; 120 this.description = copyFrom.description; 121 this.operationId = copyFrom.operationId; 122 this.externalDocs = copyFrom.externalDocs; 123 if (nn(copyFrom.parameters)) 124 parameters.addAll(copyOf(copyFrom.parameters, Parameter::copy)); 125 this.requestBody = copyFrom.requestBody; 126 if (nn(copyFrom.responses)) 127 responses.putAll(copyFrom.responses); 128 if (nn(copyFrom.callbacks)) 129 callbacks.putAll(copyFrom.callbacks); 130 this.deprecated = copyFrom.deprecated; 131 if (nn(copyFrom.security)) 132 security.addAll(copyOf(copyFrom.security, SecurityRequirement::copy)); 133 if (nn(copyFrom.servers)) 134 servers.addAll(copyOf(copyFrom.servers, Server::copy)); 135 } 136 137 /** 138 * Bean property fluent setter: <property>callbacks</property>. 139 * 140 * <p> 141 * A map of possible out-of band callbacks related to the parent operation. 142 * 143 * @param name 144 * The name of the callback. 145 * <br>Must not be <jk>null</jk>. 146 * @param callback 147 * The callback object. 148 * <br>Must not be <jk>null</jk>. 149 * @return This object. 150 */ 151 public Operation addCallback(String name, Callback callback) { 152 assertArgNotNull("name", name); 153 assertArgNotNull("callback", callback); 154 callbacks.put(name, callback); 155 return this; 156 } 157 158 /** 159 * Bean property fluent setter: <property>parameters</property>. 160 * 161 * <p> 162 * A list of parameters that are applicable for this operation. 163 * 164 * @param values 165 * The values to add to this property. 166 * <br>Ignored if <jk>null</jk>. 167 * @return This object. 168 */ 169 public Operation addParameters(Collection<Parameter> values) { 170 if (nn(values)) 171 parameters.addAll(values); 172 return this; 173 } 174 175 /** 176 * Bean property fluent setter: <property>parameters</property>. 177 * 178 * <p> 179 * A list of parameters that are applicable for this operation. 180 * 181 * @param values 182 * The values to add to this property. 183 * <br>Ignored if <jk>null</jk>. 184 * @return This object. 185 */ 186 public Operation addParameters(Parameter...values) { 187 if (nn(values)) 188 for (var v : values) 189 if (nn(v)) 190 parameters.add(v); 191 return this; 192 } 193 194 /** 195 * Bean property fluent setter: <property>responses</property>. 196 * 197 * <p> 198 * The list of possible responses as they are returned from executing this operation. 199 * 200 * @param statusCode 201 * The status code for the response. 202 * <br>Must not be <jk>null</jk>. 203 * @param response 204 * The response object. 205 * <br>Must not be <jk>null</jk>. 206 * @return This object. 207 */ 208 public Operation addResponse(String statusCode, Response response) { 209 assertArgNotNull("statusCode", statusCode); 210 assertArgNotNull("response", response); 211 responses.put(statusCode, response); 212 return this; 213 } 214 215 /** 216 * Bean property fluent setter: <property>security</property>. 217 * 218 * <p> 219 * A declaration of which security mechanisms can be used for this operation. 220 * 221 * @param values 222 * The values to add to this property. 223 * <br>Ignored if <jk>null</jk>. 224 * @return This object. 225 */ 226 public Operation addSecurity(Collection<SecurityRequirement> values) { 227 if (nn(values)) 228 security.addAll(values); 229 return this; 230 } 231 232 /** 233 * Bean property fluent setter: <property>security</property>. 234 * 235 * <p> 236 * A declaration of which security mechanisms can be used for this operation. 237 * 238 * @param values 239 * The values to add to this property. 240 * <br>Ignored if <jk>null</jk>. 241 * @return This object. 242 */ 243 public Operation addSecurity(SecurityRequirement...values) { 244 if (nn(values)) 245 for (var v : values) 246 if (nn(v)) 247 security.add(v); 248 return this; 249 } 250 251 /** 252 * Bean property fluent setter: <property>servers</property>. 253 * 254 * <p> 255 * An alternative server array to service this operation. 256 * 257 * @param values 258 * The values to add to this property. 259 * <br>Ignored if <jk>null</jk>. 260 * @return This object. 261 */ 262 public Operation addServers(Collection<Server> values) { 263 if (nn(values)) 264 servers.addAll(values); 265 return this; 266 } 267 268 /** 269 * Bean property fluent setter: <property>servers</property>. 270 * 271 * <p> 272 * An alternative server array to service this operation. 273 * 274 * @param values 275 * The values to add to this property. 276 * <br>Ignored if <jk>null</jk>. 277 * @return This object. 278 */ 279 public Operation addServers(Server...values) { 280 if (nn(values)) 281 for (var v : values) 282 if (nn(v)) 283 servers.add(v); 284 return this; 285 } 286 287 /** 288 * Bean property appender: <property>tags</property>. 289 * 290 * <p> 291 * A list of tags for API documentation control. 292 * 293 * @param values 294 * The values to add to this property. 295 * <br>Ignored if <jk>null</jk>. 296 * @return This object. 297 */ 298 public Operation addTags(Collection<String> values) { 299 if (nn(values)) 300 tags.addAll(values); 301 return this; 302 } 303 304 /** 305 * Bean property appender: <property>tags</property>. 306 * 307 * <p> 308 * A list of tags for API documentation control. 309 * 310 * @param values 311 * The values to add to this property. 312 * <br>Ignored if <jk>null</jk>. 313 * @return This object. 314 */ 315 public Operation addTags(String...values) { 316 if (nn(values)) 317 for (var v : values) 318 if (nn(v)) 319 tags.add(v); 320 return this; 321 } 322 323 /** 324 * Creates a copy of this object. 325 * 326 * @return A copy of this object. 327 */ 328 public Operation copy() { 329 return new Operation(this); 330 } 331 332 @Override /* Overridden from OpenApiElement */ 333 public <T> T get(String property, Class<T> type) { 334 assertArgNotNull("property", property); 335 return switch (property) { 336 case "tags" -> toType(getTags(), type); 337 case "summary" -> toType(getSummary(), type); 338 case "description" -> toType(getDescription(), type); 339 case "operationId" -> toType(getOperationId(), type); 340 case "externalDocs" -> toType(getExternalDocs(), type); 341 case "parameters" -> toType(getParameters(), type); 342 case "requestBody" -> toType(getRequestBody(), type); 343 case "responses" -> toType(getResponses(), type); 344 case "callbacks" -> toType(getCallbacks(), type); 345 case "deprecated" -> toType(getDeprecated(), type); 346 case "security" -> toType(getSecurity(), type); 347 case "servers" -> toType(getServers(), type); 348 default -> super.get(property, type); 349 }; 350 } 351 352 /** 353 * Returns the callbacks map. 354 * 355 * @return The callbacks map. 356 */ 357 public Map<String,Callback> getCallbacks() { return nullIfEmpty(callbacks); } 358 359 /** 360 * Returns the deprecated flag. 361 * 362 * @return The deprecated flag. 363 */ 364 public Boolean getDeprecated() { return deprecated; } 365 366 /** 367 * Returns the description. 368 * 369 * @return The description. 370 */ 371 public String getDescription() { return description; } 372 373 /** 374 * Returns the external documentation. 375 * 376 * @return The external documentation. 377 */ 378 public ExternalDocumentation getExternalDocs() { return externalDocs; } 379 380 /** 381 * Returns the operation ID. 382 * 383 * @return The operation ID. 384 */ 385 public String getOperationId() { return operationId; } 386 387 /** 388 * Returns the parameter with the specified type and name. 389 * 390 * @param in The parameter in. Must not be <jk>null</jk>. 391 * @param name The parameter name. Must not be <jk>null</jk>. 392 * @return The matching parameter, or <jk>null</jk> if not found. 393 */ 394 public Parameter getParameter(String in, String name) { 395 assertArgNotNull("in", in); 396 assertArgNotNull("name", name); 397 for (var p : parameters) 398 if (eq(p.getIn(), in) && eq(p.getName(), name)) 399 return p; 400 return null; 401 } 402 403 /** 404 * Returns the parameters list. 405 * 406 * @return The parameters list. 407 */ 408 public List<Parameter> getParameters() { return nullIfEmpty(parameters); } 409 410 /** 411 * Returns the request body. 412 * 413 * @return The request body. 414 */ 415 public RequestBodyInfo getRequestBody() { return requestBody; } 416 417 /** 418 * Returns the response with the given status code. 419 * 420 * @param status The HTTP status code. 421 * @return The response, or <jk>null</jk> if not found. 422 */ 423 public Response getResponse(int status) { 424 return getResponse(String.valueOf(status)); 425 } 426 427 /** 428 * Returns the response with the given status code. 429 * 430 * @param status The HTTP status code. Must not be <jk>null</jk>. 431 * @return The response, or <jk>null</jk> if not found. 432 */ 433 public Response getResponse(String status) { 434 assertArgNotNull("status", status); 435 return responses.get(status); 436 } 437 438 /** 439 * Returns the responses map. 440 * 441 * @return The responses map. 442 */ 443 public Map<String,Response> getResponses() { return nullIfEmpty(responses); } 444 445 /** 446 * Returns the security requirements list. 447 * 448 * @return The security requirements list. 449 */ 450 public List<SecurityRequirement> getSecurity() { return nullIfEmpty(security); } 451 452 /** 453 * Returns the servers list. 454 * 455 * @return The servers list. 456 */ 457 public List<Server> getServers() { return nullIfEmpty(servers); } 458 459 /** 460 * Returns the summary. 461 * 462 * @return The summary. 463 */ 464 public String getSummary() { return summary; } 465 466 /** 467 * Returns the tags list. 468 * 469 * @return The tags list. 470 */ 471 public List<String> getTags() { return nullIfEmpty(tags); } 472 473 @Override /* Overridden from OpenApiElement */ 474 public Set<String> keySet() { 475 // @formatter:off 476 var s = setb(String.class) 477 .addIf(ne(callbacks), "callbacks") 478 .addIf(nn(deprecated), "deprecated") 479 .addIf(nn(description), "description") 480 .addIf(nn(externalDocs), "externalDocs") 481 .addIf(nn(operationId), "operationId") 482 .addIf(ne(parameters), "parameters") 483 .addIf(nn(requestBody), "requestBody") 484 .addIf(ne(responses), "responses") 485 .addIf(ne(security), "security") 486 .addIf(ne(servers), "servers") 487 .addIf(nn(summary), "summary") 488 .addIf(ne(tags), "tags") 489 .build(); 490 // @formatter:on 491 return new MultiSet<>(s, super.keySet()); 492 } 493 494 @Override /* Overridden from OpenApiElement */ 495 public Operation set(String property, Object value) { 496 assertArgNotNull("property", property); 497 return switch (property) { 498 case "callbacks" -> setCallbacks(toMapBuilder(value, String.class, Callback.class).sparse().build()); 499 case "deprecated" -> setDeprecated(toType(value, Boolean.class)); 500 case "description" -> setDescription(s(value)); 501 case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class)); 502 case "operationId" -> setOperationId(s(value)); 503 case "parameters" -> setParameters(toListBuilder(value, Parameter.class).sparse().build()); 504 case "requestBody" -> setRequestBody(toType(value, RequestBodyInfo.class)); 505 case "responses" -> setResponses(toMapBuilder(value, String.class, Response.class).sparse().build()); 506 case "security" -> setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build()); 507 case "servers" -> setServers(toListBuilder(value, Server.class).sparse().build()); 508 case "summary" -> setSummary(s(value)); 509 case "tags" -> setTags(toListBuilder(value, String.class).sparse().build()); 510 default -> { 511 super.set(property, value); 512 yield this; 513 } 514 }; 515 } 516 517 /** 518 * Sets the callbacks map. 519 * 520 * @param value The new value for this property. 521 * @return This object. 522 */ 523 public Operation setCallbacks(Map<String,Callback> value) { 524 callbacks.clear(); 525 if (nn(value)) 526 callbacks.putAll(value); 527 return this; 528 } 529 530 /** 531 * Sets the deprecated flag. 532 * 533 * @param value The new value for this property. 534 * @return This object. 535 */ 536 public Operation setDeprecated(Boolean value) { 537 deprecated = value; 538 return this; 539 } 540 541 /** 542 * Sets the description. 543 * 544 * @param value The new value for this property. 545 * @return This object. 546 */ 547 public Operation setDescription(String value) { 548 description = value; 549 return this; 550 } 551 552 /** 553 * Sets the external documentation. 554 * 555 * @param value The new value for this property. 556 * @return This object. 557 */ 558 public Operation setExternalDocs(ExternalDocumentation value) { 559 externalDocs = value; 560 return this; 561 } 562 563 /** 564 * Sets the operation ID. 565 * 566 * @param value The new value for this property. 567 * @return This object. 568 */ 569 public Operation setOperationId(String value) { 570 operationId = value; 571 return this; 572 } 573 574 /** 575 * Sets the parameters list. 576 * 577 * @param value The new value for this property. 578 * @return This object. 579 */ 580 public Operation setParameters(List<Parameter> value) { 581 parameters.clear(); 582 if (nn(value)) 583 parameters.addAll(value); 584 return this; 585 } 586 587 /** 588 * Sets the parameters list. 589 * 590 * @param value The new value for this property. 591 * @return This object. 592 */ 593 public Operation setParameters(Parameter...value) { 594 setParameters(toListBuilder(value, Parameter.class).sparse().build()); 595 return this; 596 } 597 598 /** 599 * Sets the request body. 600 * 601 * @param value The new value for this property. 602 * @return This object. 603 */ 604 public Operation setRequestBody(RequestBodyInfo value) { 605 requestBody = value; 606 return this; 607 } 608 609 /** 610 * Sets the responses map. 611 * 612 * @param value The new value for this property. 613 * @return This object. 614 */ 615 public Operation setResponses(Map<String,Response> value) { 616 responses.clear(); 617 if (nn(value)) 618 responses.putAll(value); 619 return this; 620 } 621 622 /** 623 * Sets the security requirements list. 624 * 625 * @param value The new value for this property. 626 * @return This object. 627 */ 628 public Operation setSecurity(List<SecurityRequirement> value) { 629 security.clear(); 630 if (nn(value)) 631 security.addAll(value); 632 return this; 633 } 634 635 /** 636 * Sets the security requirements list. 637 * 638 * @param value The new value for this property. 639 * @return This object. 640 */ 641 public Operation setSecurity(SecurityRequirement...value) { 642 setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build()); 643 return this; 644 } 645 646 /** 647 * Sets the servers list. 648 * 649 * @param value The new value for this property. 650 * @return This object. 651 */ 652 public Operation setServers(List<Server> value) { 653 servers.clear(); 654 if (nn(value)) 655 servers.addAll(value); 656 return this; 657 } 658 659 /** 660 * Sets the servers list. 661 * 662 * @param value The new value for this property. 663 * @return This object. 664 */ 665 public Operation setServers(Server...value) { 666 setServers(toListBuilder(value, Server.class).sparse().build()); 667 return this; 668 } 669 670 /** 671 * Sets the summary. 672 * 673 * @param value The new value for this property. 674 * @return This object. 675 */ 676 public Operation setSummary(String value) { 677 summary = value; 678 return this; 679 } 680 681 /** 682 * Sets the tags list. 683 * 684 * @param value The new value for this property. 685 * @return This object. 686 */ 687 public Operation setTags(List<String> value) { 688 tags.clear(); 689 if (nn(value)) 690 tags.addAll(value); 691 return this; 692 } 693 694 /** 695 * Sets the tags list. 696 * 697 * @param value The new value for this property. 698 * @return This object. 699 */ 700 public Operation setTags(String...value) { 701 setTags(toListBuilder(value, String.class).sparse().build()); 702 return this; 703 } 704 705 @Override /* Overridden from OpenApiElement */ 706 public Operation strict() { 707 super.strict(); 708 return this; 709 } 710 711 @Override /* Overridden from OpenApiElement */ 712 public Operation strict(Object value) { 713 super.strict(value); 714 return this; 715 } 716}