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