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.*;
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 &gt; 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 */
092@SuppressWarnings({
093   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
094})
095public class Operation extends OpenApiElement {
096
097   private final List<String> tags = list();
098   private String summary;
099   private String description;
100   private String operationId;
101   private ExternalDocumentation externalDocs;
102   private final List<Parameter> parameters = list();
103   private RequestBodyInfo requestBody;
104   private final Map<String,Response> responses = map();
105   private final Map<String,Callback> callbacks = map();
106   private Boolean deprecated;
107   private final List<SecurityRequirement> security = list();
108   private final List<Server> servers = list();
109
110   /**
111    * Default constructor.
112    */
113   public Operation() {}
114
115   /**
116    * Copy constructor.
117    *
118    * @param copyFrom The object to copy.
119    */
120   public Operation(Operation copyFrom) {
121      super(copyFrom);
122      tags.addAll(copyFrom.tags);
123      this.summary = copyFrom.summary;
124      this.description = copyFrom.description;
125      this.operationId = copyFrom.operationId;
126      this.externalDocs = copyOf(copyFrom.externalDocs);
127      parameters.addAll(copyOf(copyFrom.parameters, Parameter::copy));
128      this.requestBody = copyOf(copyFrom.requestBody);
129      responses.putAll(copyOf(copyFrom.responses, Response::copy));
130      callbacks.putAll(copyOf(copyFrom.callbacks, Callback::copy));
131      this.deprecated = copyFrom.deprecated;
132      security.addAll(copyOf(copyFrom.security, SecurityRequirement::copy));
133      servers.addAll(copyOf(copyFrom.servers, Server::copy));
134   }
135
136   /**
137    * Bean property fluent setter:  <property>callbacks</property>.
138    *
139    * <p>
140    * A map of possible out-of band callbacks related to the parent operation.
141    *
142    * @param name
143    *    The name of the callback.
144    *    <br>Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
145    * @param callback
146    *    The callback object.
147    *    <br>Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
148    * @return This object.
149    */
150   public Operation addCallback(String name, Callback callback) {
151      reqnn("name", name);
152      reqnn("callback", callback);
153      callbacks.put(name, callback);
154      return this;
155   }
156
157   /**
158    * Bean property fluent setter:  <property>parameters</property>.
159    *
160    * <p>
161    * A list of parameters that are applicable for this operation.
162    *
163    * @param values
164    *    The values to add to this property.
165    *    <br>Ignored if <jk>null</jk>.
166    * @return This object.
167    */
168   public Operation addParameters(Collection<Parameter> values) {
169      if (nn(values))
170         parameters.addAll(values);
171      return this;
172   }
173
174   /**
175    * Bean property fluent setter:  <property>parameters</property>.
176    *
177    * <p>
178    * A list of parameters that are applicable for this operation.
179    *
180    * @param values
181    *    The values to add to this property.
182    *    <br>Ignored if <jk>null</jk>.
183    * @return This object.
184    */
185   public Operation addParameters(Parameter...values) {
186      addAllNn(parameters, values);
187      return this;
188   }
189
190   /**
191    * Bean property fluent setter:  <property>responses</property>.
192    *
193    * <p>
194    * The list of possible responses as they are returned from executing this operation.
195    *
196    * @param statusCode
197    *    The status code for the response.
198    *    <br>Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
199    * @param response
200    *    The response object.
201    *    <br>Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
202    * @return This object.
203    */
204   public Operation addResponse(String statusCode, Response response) {
205      reqnn("statusCode", statusCode);
206      reqnn("response", response);
207      responses.put(statusCode, response);
208      return this;
209   }
210
211   /**
212    * Bean property fluent setter:  <property>security</property>.
213    *
214    * <p>
215    * A declaration of which security mechanisms can be used for this operation.
216    *
217    * @param values
218    *    The values to add to this property.
219    *    <br>Ignored if <jk>null</jk>.
220    * @return This object.
221    */
222   public Operation addSecurity(Collection<SecurityRequirement> values) {
223      if (nn(values))
224         security.addAll(values);
225      return this;
226   }
227
228   /**
229    * Bean property fluent setter:  <property>security</property>.
230    *
231    * <p>
232    * A declaration of which security mechanisms can be used for this operation.
233    *
234    * @param values
235    *    The values to add to this property.
236    *    <br>Ignored if <jk>null</jk>.
237    * @return This object.
238    */
239   public Operation addSecurity(SecurityRequirement...values) {
240      addAllNn(security, values);
241      return this;
242   }
243
244   /**
245    * Bean property fluent setter:  <property>servers</property>.
246    *
247    * <p>
248    * An alternative server array to service this operation.
249    *
250    * @param values
251    *    The values to add to this property.
252    *    <br>Ignored if <jk>null</jk>.
253    * @return This object.
254    */
255   public Operation addServers(Collection<Server> values) {
256      if (nn(values))
257         servers.addAll(values);
258      return this;
259   }
260
261   /**
262    * Bean property fluent setter:  <property>servers</property>.
263    *
264    * <p>
265    * An alternative server array to service this operation.
266    *
267    * @param values
268    *    The values to add to this property.
269    *    <br>Ignored if <jk>null</jk>.
270    * @return This object.
271    */
272   public Operation addServers(Server...values) {
273      addAllNn(servers, values);
274      return this;
275   }
276
277   /**
278    * Bean property appender:  <property>tags</property>.
279    *
280    * <p>
281    * A list of tags for API documentation control.
282    *
283    * @param values
284    *    The values to add to this property.
285    *    <br>Ignored if <jk>null</jk>.
286    * @return This object.
287    */
288   public Operation addTags(Collection<String> values) {
289      if (nn(values))
290         tags.addAll(values);
291      return this;
292   }
293
294   /**
295    * Bean property appender:  <property>tags</property>.
296    *
297    * <p>
298    * A list of tags for API documentation control.
299    *
300    * @param values
301    *    The values to add to this property.
302    *    <br>Ignored if <jk>null</jk>.
303    * @return This object.
304    */
305   public Operation addTags(String...values) {
306      addAllNn(tags, values);
307      return this;
308   }
309
310   /**
311    * Creates a copy of this object.
312    *
313    * @return A copy of this object.
314    */
315   public Operation copy() {
316      return new Operation(this);
317   }
318
319   @Override /* Overridden from OpenApiElement */
320   public <T> T get(String property, Class<T> type) {
321      reqnn("property", property);
322      return switch (property) {
323         case "tags" -> toType(getTags(), type);
324         case "summary" -> toType(getSummary(), type);
325         case "description" -> toType(getDescription(), type);
326         case "operationId" -> toType(getOperationId(), type);
327         case "externalDocs" -> toType(getExternalDocs(), type);
328         case "parameters" -> toType(getParameters(), type);
329         case "requestBody" -> toType(getRequestBody(), type);
330         case "responses" -> toType(getResponses(), type);
331         case "callbacks" -> toType(getCallbacks(), type);
332         case "deprecated" -> toType(getDeprecated(), type);
333         case "security" -> toType(getSecurity(), type);
334         case "servers" -> toType(getServers(), type);
335         default -> super.get(property, type);
336      };
337   }
338
339   /**
340    * Returns the callbacks map.
341    *
342    * @return The callbacks map, or <jk>null</jk> if not set.
343    */
344   public Map<String,Callback> getCallbacks() { return nie(callbacks); }
345
346   /**
347    * Returns the deprecated flag.
348    *
349    * @return The deprecated flag, or <jk>null</jk> if not set.
350    */
351   public Boolean getDeprecated() { return deprecated; }
352
353   /**
354    * Returns the description.
355    *
356    * @return The description, or <jk>null</jk> if not set.
357    */
358   public String getDescription() { return description; }
359
360   /**
361    * Returns the external documentation.
362    *
363    * @return The external documentation, or <jk>null</jk> if not set.
364    */
365   public ExternalDocumentation getExternalDocs() { return externalDocs; }
366
367   /**
368    * Returns the operation ID.
369    *
370    * @return The operation ID, or <jk>null</jk> if not set.
371    */
372   public String getOperationId() { return operationId; }
373
374   /**
375    * Returns the parameter with the specified type and name.
376    *
377    * @param in The parameter in.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
378    * @param name The parameter name.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
379    * @return The matching parameter, or <jk>null</jk> if not found.
380    */
381   public Parameter getParameter(String in, String name) {
382      reqnn("in", in);
383      reqnn("name", name);
384      for (var p : parameters)
385         if (eq(p.getIn(), in) && eq(p.getName(), name))
386            return p;
387      return null;
388   }
389
390   /**
391    * Returns the parameters list.
392    *
393    * @return The parameters list, or <jk>null</jk> if not set.
394    */
395   public List<Parameter> getParameters() { return nie(parameters); }
396
397   /**
398    * Returns the request body.
399    *
400    * @return The request body, or <jk>null</jk> if not set.
401    */
402   public RequestBodyInfo getRequestBody() { return requestBody; }
403
404   /**
405    * Returns the response with the given status code.
406    *
407    * @param status The HTTP status code.
408    * @return The response, or <jk>null</jk> if not found.
409    */
410   public Response getResponse(int status) {
411      return getResponse(String.valueOf(status));
412   }
413
414   /**
415    * Returns the response with the given status code.
416    *
417    * @param status The HTTP status code.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
418    * @return The response, or <jk>null</jk> if not found.
419    */
420   public Response getResponse(String status) {
421      reqnn("status", status);
422      return responses.get(status);
423   }
424
425   /**
426    * Returns the responses map.
427    *
428    * @return The responses map, or <jk>null</jk> if not set.
429    */
430   public Map<String,Response> getResponses() { return nie(responses); }
431
432   /**
433    * Returns the security requirements list.
434    *
435    * @return The security requirements list, or <jk>null</jk> if not set.
436    */
437   public List<SecurityRequirement> getSecurity() { return nie(security); }
438
439   /**
440    * Returns the servers list.
441    *
442    * @return The servers list, or <jk>null</jk> if not set.
443    */
444   public List<Server> getServers() { return nie(servers); }
445
446   /**
447    * Returns the summary.
448    *
449    * @return The summary, or <jk>null</jk> if not set.
450    */
451   public String getSummary() { return summary; }
452
453   /**
454    * Returns the tags list.
455    *
456    * @return The tags list, or <jk>null</jk> if not set.
457    */
458   public List<String> getTags() { return nie(tags); }
459
460   @Override /* Overridden from OpenApiElement */
461   public Set<String> keySet() {
462      // @formatter:off
463      var s = stb(String.class)
464         .addIf(ine(callbacks), "callbacks")
465         .addIf(nn(deprecated), "deprecated")
466         .addIf(nn(description), "description")
467         .addIf(nn(externalDocs), "externalDocs")
468         .addIf(nn(operationId), "operationId")
469         .addIf(ine(parameters), "parameters")
470         .addIf(nn(requestBody), "requestBody")
471         .addIf(ine(responses), "responses")
472         .addIf(ine(security), "security")
473         .addIf(ine(servers), "servers")
474         .addIf(nn(summary), "summary")
475         .addIf(ine(tags), "tags")
476         .build();
477      // @formatter:on
478      return new MultiSet<>(s, super.keySet());
479   }
480
481   @Override /* Overridden from OpenApiElement */
482   public Operation set(String property, Object value) {
483      reqnn("property", property);
484      return switch (property) {
485         case "callbacks" -> setCallbacks(toMapBuilder(value, String.class, Callback.class).sparse().build());
486         case "deprecated" -> setDeprecated(toType(value, Boolean.class));
487         case "description" -> setDescription(s(value));
488         case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class));
489         case "operationId" -> setOperationId(s(value));
490         case "parameters" -> setParameters(toListBuilder(value, Parameter.class).sparse().build());
491         case "requestBody" -> setRequestBody(toType(value, RequestBodyInfo.class));
492         case "responses" -> setResponses(toMapBuilder(value, String.class, Response.class).sparse().build());
493         case "security" -> setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build());
494         case "servers" -> setServers(toListBuilder(value, Server.class).sparse().build());
495         case "summary" -> setSummary(s(value));
496         case "tags" -> setTags(toListBuilder(value, String.class).sparse().build());
497         default -> {
498            super.set(property, value);
499            yield this;
500         }
501      };
502   }
503
504   /**
505    * Sets the callbacks map.
506    *
507    * @param value The new value for this property.
508    * @return This object.
509    */
510   public Operation setCallbacks(Map<String,Callback> value) {
511      callbacks.clear();
512      if (nn(value))
513         callbacks.putAll(value);
514      return this;
515   }
516
517   /**
518    * Sets the deprecated flag.
519    *
520    * @param value The new value for this property.
521    * @return This object.
522    */
523   public Operation setDeprecated(Boolean value) {
524      deprecated = value;
525      return this;
526   }
527
528   /**
529    * Sets the description.
530    *
531    * @param value The new value for this property.
532    * @return This object.
533    */
534   public Operation setDescription(String value) {
535      description = value;
536      return this;
537   }
538
539   /**
540    * Sets the external documentation.
541    *
542    * @param value The new value for this property.
543    * @return This object.
544    */
545   public Operation setExternalDocs(ExternalDocumentation value) {
546      externalDocs = value;
547      return this;
548   }
549
550   /**
551    * Sets the operation ID.
552    *
553    * @param value The new value for this property.
554    * @return This object.
555    */
556   public Operation setOperationId(String value) {
557      operationId = value;
558      return this;
559   }
560
561   /**
562    * Sets the parameters list.
563    *
564    * @param value The new value for this property.
565    * @return This object.
566    */
567   public Operation setParameters(List<Parameter> value) {
568      parameters.clear();
569      if (nn(value))
570         parameters.addAll(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(Parameter...value) {
581      setParameters(toListBuilder(value, Parameter.class).sparse().build());
582      return this;
583   }
584
585   /**
586    * Sets the request body.
587    *
588    * @param value The new value for this property.
589    * @return This object.
590    */
591   public Operation setRequestBody(RequestBodyInfo value) {
592      requestBody = value;
593      return this;
594   }
595
596   /**
597    * Sets the responses map.
598    *
599    * @param value The new value for this property.
600    * @return This object.
601    */
602   public Operation setResponses(Map<String,Response> value) {
603      responses.clear();
604      if (nn(value))
605         responses.putAll(value);
606      return this;
607   }
608
609   /**
610    * Sets the security requirements list.
611    *
612    * @param value The new value for this property.
613    * @return This object.
614    */
615   public Operation setSecurity(List<SecurityRequirement> value) {
616      security.clear();
617      if (nn(value))
618         security.addAll(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(SecurityRequirement...value) {
629      setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build());
630      return this;
631   }
632
633   /**
634    * Sets the servers list.
635    *
636    * @param value The new value for this property.
637    * @return This object.
638    */
639   public Operation setServers(List<Server> value) {
640      servers.clear();
641      if (nn(value))
642         servers.addAll(value);
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(Server...value) {
653      setServers(toListBuilder(value, Server.class).sparse().build());
654      return this;
655   }
656
657   /**
658    * Sets the summary.
659    *
660    * @param value The new value for this property.
661    * @return This object.
662    */
663   public Operation setSummary(String value) {
664      summary = value;
665      return this;
666   }
667
668   /**
669    * Sets the tags list.
670    *
671    * @param value The new value for this property.
672    * @return This object.
673    */
674   public Operation setTags(List<String> value) {
675      tags.clear();
676      if (nn(value))
677         tags.addAll(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(String...value) {
688      setTags(toListBuilder(value, String.class).sparse().build());
689      return this;
690   }
691
692   @Override /* Overridden from OpenApiElement */
693   public Operation strict() {
694      super.strict();
695      return this;
696   }
697
698   @Override /* Overridden from OpenApiElement */
699   public Operation strict(Object value) {
700      super.strict(value);
701      return this;
702   }
703}