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 &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 = 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}