Skip to main content

utoipa/openapi/
path.rs

1//! Implements [OpenAPI Path Object][paths] types.
2//!
3//! [paths]: https://spec.openapis.org/oas/latest.html#paths-object
4use crate::Path;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::BTreeMap;
8
9use super::{
10    builder,
11    content::Content,
12    extensions::Extensions,
13    request_body::RequestBody,
14    response::{Response, Responses},
15    security::SecurityRequirement,
16    set_value, Deprecated, ExternalDocs, Ref, RefOr, Required, Schema, Server,
17};
18
19#[cfg(not(feature = "preserve_path_order"))]
20#[allow(missing_docs)]
21#[doc(hidden)]
22pub type PathsMap<K, V> = std::collections::BTreeMap<K, V>;
23#[cfg(feature = "preserve_path_order")]
24#[allow(missing_docs)]
25#[doc(hidden)]
26pub type PathsMap<K, V> = indexmap::IndexMap<K, V>;
27
28/// OpenAPI Callback Object mapping callback expressions to path items.
29pub type Callback = BTreeMap<String, RefOr<PathItem>>;
30
31builder! {
32    PathsBuilder;
33
34    /// Implements [OpenAPI Paths Object][paths].
35    ///
36    /// Holds relative paths to matching endpoints and operations. The path is appended to the url
37    /// from [`Server`] object to construct a full url for endpoint.
38    ///
39    /// [paths]: https://spec.openapis.org/oas/latest.html#paths-object
40    #[non_exhaustive]
41    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
42    #[cfg_attr(feature = "debug", derive(Debug))]
43    pub struct Paths {
44        /// Map of relative paths with [`PathItem`]s holding [`Operation`]s matching
45        /// api endpoints.
46        #[serde(flatten)]
47        pub paths: PathsMap<String, PathItem>,
48
49        /// Optional extensions "x-something".
50        #[serde(skip_serializing_if = "Option::is_none", flatten)]
51        pub extensions: Option<Extensions>,
52    }
53}
54
55impl Paths {
56    /// Construct a new [`Paths`] object.
57    pub fn new() -> Self {
58        Default::default()
59    }
60
61    /// Return _`Option`_ of reference to [`PathItem`] by given relative path _`P`_ if one exists
62    /// in [`Paths::paths`] map. Otherwise will return `None`.
63    ///
64    /// # Examples
65    ///
66    /// _**Get user path item.**_
67    /// ```rust
68    /// # use utoipa::openapi::path::{Paths, HttpMethod};
69    /// # let paths = Paths::new();
70    /// let path_item = paths.get_path_item("/api/v1/user");
71    /// ```
72    pub fn get_path_item<P: AsRef<str>>(&self, path: P) -> Option<&PathItem> {
73        self.paths.get(path.as_ref())
74    }
75
76    /// Return _`Option`_ of reference to [`Operation`] from map of paths or `None` if not found.
77    ///
78    /// * First will try to find [`PathItem`] by given relative path _`P`_ e.g. `"/api/v1/user"`.
79    /// * Then tries to find [`Operation`] from [`PathItem`]'s operations by given [`HttpMethod`].
80    ///
81    /// # Examples
82    ///
83    /// _**Get user operation from paths.**_
84    /// ```rust
85    /// # use utoipa::openapi::path::{Paths, HttpMethod};
86    /// # let paths = Paths::new();
87    /// let operation = paths.get_path_operation("/api/v1/user", HttpMethod::Get);
88    /// ```
89    pub fn get_path_operation<P: AsRef<str>>(
90        &self,
91        path: P,
92        http_method: HttpMethod,
93    ) -> Option<&Operation> {
94        self.paths
95            .get(path.as_ref())
96            .and_then(|path| match http_method {
97                HttpMethod::Get => path.get.as_ref(),
98                HttpMethod::Put => path.put.as_ref(),
99                HttpMethod::Post => path.post.as_ref(),
100                HttpMethod::Delete => path.delete.as_ref(),
101                HttpMethod::Options => path.options.as_ref(),
102                HttpMethod::Head => path.head.as_ref(),
103                HttpMethod::Patch => path.patch.as_ref(),
104                HttpMethod::Trace => path.trace.as_ref(),
105            })
106    }
107
108    /// Append path operation to the list of paths.
109    ///
110    /// Method accepts three arguments; `path` to add operation for, `http_methods` list of
111    /// allowed HTTP methods for the [`Operation`] and `operation` to be added under the _`path`_.
112    ///
113    /// If _`path`_ already exists, the provided [`Operation`] will be set to existing path item for
114    /// given list of [`HttpMethod`]s.
115    pub fn add_path_operation<P: AsRef<str>, O: Into<Operation>>(
116        &mut self,
117        path: P,
118        http_methods: Vec<HttpMethod>,
119        operation: O,
120    ) {
121        let path = path.as_ref();
122        let operation = operation.into();
123        if let Some(existing_item) = self.paths.get_mut(path) {
124            for http_method in http_methods {
125                match http_method {
126                    HttpMethod::Get => existing_item.get = Some(operation.clone()),
127                    HttpMethod::Put => existing_item.put = Some(operation.clone()),
128                    HttpMethod::Post => existing_item.post = Some(operation.clone()),
129                    HttpMethod::Delete => existing_item.delete = Some(operation.clone()),
130                    HttpMethod::Options => existing_item.options = Some(operation.clone()),
131                    HttpMethod::Head => existing_item.head = Some(operation.clone()),
132                    HttpMethod::Patch => existing_item.patch = Some(operation.clone()),
133                    HttpMethod::Trace => existing_item.trace = Some(operation.clone()),
134                };
135            }
136        } else {
137            self.paths.insert(
138                String::from(path),
139                PathItem::from_http_methods(http_methods, operation),
140            );
141        }
142    }
143
144    /// Merge _`other_paths`_ into `self`. On conflicting path the path item operations will be
145    /// merged into existing [`PathItem`]. Otherwise path with [`PathItem`] will be appended to
146    /// `self`. All [`Extensions`] will be merged from _`other_paths`_ into `self`.
147    pub fn merge(&mut self, other_paths: Paths) {
148        for (path, that) in other_paths.paths {
149            if let Some(this) = self.paths.get_mut(&path) {
150                this.merge_operations(that);
151            } else {
152                self.paths.insert(path, that);
153            }
154        }
155
156        if let Some(other_paths_extensions) = other_paths.extensions {
157            let paths_extensions = self.extensions.get_or_insert(Extensions::default());
158            paths_extensions.merge(other_paths_extensions);
159        }
160    }
161}
162
163impl PathsBuilder {
164    /// Append [`PathItem`] with path to map of paths. If path already exists it will merge [`Operation`]s of
165    /// [`PathItem`] with already found path item operations.
166    pub fn path<I: Into<String>>(mut self, path: I, item: PathItem) -> Self {
167        let path_string = path.into();
168        if let Some(existing_item) = self.paths.get_mut(&path_string) {
169            existing_item.merge_operations(item);
170        } else {
171            self.paths.insert(path_string, item);
172        }
173
174        self
175    }
176
177    /// Add extensions to the paths section.
178    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
179        set_value!(self extensions extensions)
180    }
181
182    /// Appends a [`Path`] to map of paths. Method must be called with one generic argument that
183    /// implements [`trait@Path`] trait.
184    ///
185    /// # Examples
186    ///
187    /// _**Append `MyPath` content to the paths.**_
188    /// ```rust
189    /// # struct MyPath;
190    /// # impl utoipa::Path for MyPath {
191    /// #   fn methods() -> Vec<utoipa::openapi::path::HttpMethod> { vec![] }
192    /// #   fn path() -> String { String::new() }
193    /// #   fn operation() -> utoipa::openapi::path::Operation {
194    /// #        utoipa::openapi::path::Operation::new()
195    /// #   }
196    /// # }
197    /// let paths = utoipa::openapi::path::PathsBuilder::new();
198    /// let _ = paths.path_from::<MyPath>();
199    /// ```
200    pub fn path_from<P: Path>(self) -> Self {
201        let methods = P::methods();
202        let operation = P::operation();
203
204        // for one operation method avoid clone
205        let path_item = if methods.len() == 1 {
206            PathItem::new(
207                methods
208                    .into_iter()
209                    .next()
210                    .expect("must have one operation method"),
211                operation,
212            )
213        } else {
214            methods
215                .into_iter()
216                .fold(PathItemBuilder::new(), |path_item, method| {
217                    path_item.operation(method, operation.clone())
218                })
219                .build()
220        };
221
222        self.path(P::path(), path_item)
223    }
224}
225
226builder! {
227    PathItemBuilder;
228
229    /// Implements [OpenAPI Path Item Object][path_item] what describes [`Operation`]s available on
230    /// a single path.
231    ///
232    /// [path_item]: https://spec.openapis.org/oas/latest.html#path-item-object
233    #[non_exhaustive]
234    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
235    #[cfg_attr(feature = "debug", derive(Debug))]
236    #[serde(rename_all = "camelCase")]
237    pub struct PathItem {
238        /// Allows referencing another Path Item Object.
239        #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
240        pub ref_path: Option<String>,
241
242        /// Optional summary intended to apply all operations in this [`PathItem`].
243        #[serde(skip_serializing_if = "Option::is_none")]
244        pub summary: Option<String>,
245
246        /// Optional description intended to apply all operations in this [`PathItem`].
247        /// Description supports markdown syntax.
248        #[serde(skip_serializing_if = "Option::is_none")]
249        pub description: Option<String>,
250
251        /// Alternative [`Server`] array to serve all [`Operation`]s in this [`PathItem`] overriding
252        /// the global server array.
253        #[serde(skip_serializing_if = "Option::is_none")]
254        pub servers: Option<Vec<Server>>,
255
256        /// List of [`Parameter`]s common to all [`Operation`]s in this [`PathItem`]. Parameters cannot
257        /// contain duplicate parameters. They can be overridden in [`Operation`] level but cannot be
258        /// removed there.
259        #[serde(skip_serializing_if = "Option::is_none")]
260        pub parameters: Option<Vec<RefOr<Parameter>>>,
261
262        /// Get [`Operation`] for the [`PathItem`].
263        #[serde(skip_serializing_if = "Option::is_none")]
264        pub get: Option<Operation>,
265
266        /// Put [`Operation`] for the [`PathItem`].
267        #[serde(skip_serializing_if = "Option::is_none")]
268        pub put: Option<Operation>,
269
270        /// Post [`Operation`] for the [`PathItem`].
271        #[serde(skip_serializing_if = "Option::is_none")]
272        pub post: Option<Operation>,
273
274        /// Delete [`Operation`] for the [`PathItem`].
275        #[serde(skip_serializing_if = "Option::is_none")]
276        pub delete: Option<Operation>,
277
278        /// Options [`Operation`] for the [`PathItem`].
279        #[serde(skip_serializing_if = "Option::is_none")]
280        pub options: Option<Operation>,
281
282        /// Head [`Operation`] for the [`PathItem`].
283        #[serde(skip_serializing_if = "Option::is_none")]
284        pub head: Option<Operation>,
285
286        /// Patch [`Operation`] for the [`PathItem`].
287        #[serde(skip_serializing_if = "Option::is_none")]
288        pub patch: Option<Operation>,
289
290        /// Trace [`Operation`] for the [`PathItem`].
291        #[serde(skip_serializing_if = "Option::is_none")]
292        pub trace: Option<Operation>,
293
294        /// Query [`Operation`] for the [`PathItem`].
295        #[serde(skip_serializing_if = "Option::is_none")]
296        pub query: Option<Operation>,
297
298        /// Operation used for HTTP methods or other operations not explicitly listed on this path.
299        #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
300        pub additional_operations: BTreeMap<String, Operation>,
301
302        /// Optional extensions "x-something".
303        #[serde(skip_serializing_if = "Option::is_none", flatten)]
304        pub extensions: Option<Extensions>,
305    }
306}
307
308impl PathItem {
309    /// Construct a new [`PathItem`] with provided [`Operation`] mapped to given [`HttpMethod`].
310    pub fn new<O: Into<Operation>>(http_method: HttpMethod, operation: O) -> Self {
311        let mut path_item = Self::default();
312        match http_method {
313            HttpMethod::Get => path_item.get = Some(operation.into()),
314            HttpMethod::Put => path_item.put = Some(operation.into()),
315            HttpMethod::Post => path_item.post = Some(operation.into()),
316            HttpMethod::Delete => path_item.delete = Some(operation.into()),
317            HttpMethod::Options => path_item.options = Some(operation.into()),
318            HttpMethod::Head => path_item.head = Some(operation.into()),
319            HttpMethod::Patch => path_item.patch = Some(operation.into()),
320            HttpMethod::Trace => path_item.trace = Some(operation.into()),
321        };
322
323        path_item
324    }
325
326    /// Constructs a new [`PathItem`] with given [`Operation`] set for provided [`HttpMethod`]s.
327    pub fn from_http_methods<I: IntoIterator<Item = HttpMethod>, O: Into<Operation>>(
328        http_methods: I,
329        operation: O,
330    ) -> Self {
331        let mut path_item = Self::default();
332        let operation = operation.into();
333        for method in http_methods {
334            match method {
335                HttpMethod::Get => path_item.get = Some(operation.clone()),
336                HttpMethod::Put => path_item.put = Some(operation.clone()),
337                HttpMethod::Post => path_item.post = Some(operation.clone()),
338                HttpMethod::Delete => path_item.delete = Some(operation.clone()),
339                HttpMethod::Options => path_item.options = Some(operation.clone()),
340                HttpMethod::Head => path_item.head = Some(operation.clone()),
341                HttpMethod::Patch => path_item.patch = Some(operation.clone()),
342                HttpMethod::Trace => path_item.trace = Some(operation.clone()),
343            };
344        }
345
346        path_item
347    }
348
349    /// Merge all defined [`Operation`]s from given [`PathItem`] to `self` if `self` does not have
350    /// existing operation.
351    pub fn merge_operations(&mut self, path_item: PathItem) {
352        if path_item.get.is_some() && self.get.is_none() {
353            self.get = path_item.get;
354        }
355        if path_item.put.is_some() && self.put.is_none() {
356            self.put = path_item.put;
357        }
358        if path_item.post.is_some() && self.post.is_none() {
359            self.post = path_item.post;
360        }
361        if path_item.delete.is_some() && self.delete.is_none() {
362            self.delete = path_item.delete;
363        }
364        if path_item.options.is_some() && self.options.is_none() {
365            self.options = path_item.options;
366        }
367        if path_item.head.is_some() && self.head.is_none() {
368            self.head = path_item.head;
369        }
370        if path_item.patch.is_some() && self.patch.is_none() {
371            self.patch = path_item.patch;
372        }
373        if path_item.trace.is_some() && self.trace.is_none() {
374            self.trace = path_item.trace;
375        }
376        if path_item.query.is_some() && self.query.is_none() {
377            self.query = path_item.query;
378        }
379        for (method, operation) in path_item.additional_operations {
380            self.additional_operations
381                .entry(method)
382                .or_insert(operation);
383        }
384    }
385}
386
387impl PathItemBuilder {
388    /// Add or change reference to another Path Item Object.
389    pub fn ref_path<S: Into<String>>(mut self, ref_path: Option<S>) -> Self {
390        set_value!(self ref_path ref_path.map(Into::into))
391    }
392
393    /// Append a new [`Operation`] by [`HttpMethod`] to this [`PathItem`]. Operations can
394    /// hold only one operation per [`HttpMethod`].
395    pub fn operation<O: Into<Operation>>(mut self, http_method: HttpMethod, operation: O) -> Self {
396        match http_method {
397            HttpMethod::Get => self.get = Some(operation.into()),
398            HttpMethod::Put => self.put = Some(operation.into()),
399            HttpMethod::Post => self.post = Some(operation.into()),
400            HttpMethod::Delete => self.delete = Some(operation.into()),
401            HttpMethod::Options => self.options = Some(operation.into()),
402            HttpMethod::Head => self.head = Some(operation.into()),
403            HttpMethod::Patch => self.patch = Some(operation.into()),
404            HttpMethod::Trace => self.trace = Some(operation.into()),
405        };
406
407        self
408    }
409
410    /// Add or change query [`Operation`] for this [`PathItem`].
411    pub fn query<O: Into<Operation>>(mut self, operation: Option<O>) -> Self {
412        set_value!(self query operation.map(Into::into))
413    }
414
415    /// Add or change operation used for HTTP methods or other operations not explicitly listed on
416    /// this path.
417    pub fn additional_operations<
418        I: IntoIterator<Item = (S, O)>,
419        S: Into<String>,
420        O: Into<Operation>,
421    >(
422        mut self,
423        additional_operations: I,
424    ) -> Self {
425        self.additional_operations.extend(
426            additional_operations
427                .into_iter()
428                .map(|(method, operation)| (method.into(), operation.into())),
429        );
430
431        self
432    }
433
434    /// Add operation used for an HTTP method not explicitly listed on this path.
435    pub fn additional_operation<S: Into<String>, O: Into<Operation>>(
436        mut self,
437        method: S,
438        operation: O,
439    ) -> Self {
440        self.additional_operations
441            .insert(method.into(), operation.into());
442
443        self
444    }
445
446    /// Add or change summary intended to apply all operations in this [`PathItem`].
447    pub fn summary<S: Into<String>>(mut self, summary: Option<S>) -> Self {
448        set_value!(self summary summary.map(|summary| summary.into()))
449    }
450
451    /// Add or change optional description intended to apply all operations in this [`PathItem`].
452    /// Description supports markdown syntax.
453    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
454        set_value!(self description description.map(|description| description.into()))
455    }
456
457    /// Add list of alternative [`Server`]s to serve all [`Operation`]s in this [`PathItem`] overriding
458    /// the global server array.
459    pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
460        set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
461    }
462
463    /// Append list of [`Parameter`]s common to all [`Operation`]s to this [`PathItem`].
464    pub fn parameters<I: IntoIterator<Item = Parameter>>(mut self, parameters: Option<I>) -> Self {
465        set_value!(self parameters parameters.map(|parameters| parameters.into_iter().map(Into::into).collect()))
466    }
467
468    /// Append list of parameter references common to all [`Operation`]s to this [`PathItem`].
469    pub fn parameter_refs<I: IntoIterator<Item = Ref>>(mut self, refs: Option<I>) -> Self {
470        set_value!(self parameters refs.map(|refs| refs.into_iter().map(RefOr::Ref).collect()))
471    }
472
473    /// Add openapi extensions (x-something) to this [`PathItem`].
474    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
475        set_value!(self extensions extensions)
476    }
477}
478
479/// HTTP method of the operation.
480///
481/// List of supported HTTP methods <https://spec.openapis.org/oas/latest.html#path-item-object>
482#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
483#[serde(rename_all = "lowercase")]
484#[cfg_attr(feature = "debug", derive(Debug))]
485pub enum HttpMethod {
486    /// Type mapping for HTTP _GET_ request.
487    Get,
488    /// Type mapping for HTTP _POST_ request.
489    Post,
490    /// Type mapping for HTTP _PUT_ request.
491    Put,
492    /// Type mapping for HTTP _DELETE_ request.
493    Delete,
494    /// Type mapping for HTTP _OPTIONS_ request.
495    Options,
496    /// Type mapping for HTTP _HEAD_ request.
497    Head,
498    /// Type mapping for HTTP _PATCH_ request.
499    Patch,
500    /// Type mapping for HTTP _TRACE_ request.
501    Trace,
502}
503
504builder! {
505    OperationBuilder;
506
507    /// Implements [OpenAPI Operation Object][operation] object.
508    ///
509    /// [operation]: https://spec.openapis.org/oas/latest.html#operation-object
510    #[non_exhaustive]
511    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
512    #[cfg_attr(feature = "debug", derive(Debug))]
513    #[serde(rename_all = "camelCase")]
514    pub struct Operation {
515        /// List of tags used for grouping operations.
516        ///
517        /// When used with derive [`#[utoipa::path(...)]`][derive_path] attribute macro the default
518        /// value used will be resolved from handler path provided in `#[openapi(paths(...))]` with
519        /// [`#[derive(OpenApi)]`][derive_openapi] macro. If path resolves to `None` value `crate` will
520        /// be used by default.
521        ///
522        /// [derive_path]: ../../attr.path.html
523        /// [derive_openapi]: ../../derive.OpenApi.html
524        #[serde(skip_serializing_if = "Option::is_none")]
525        pub tags: Option<Vec<String>>,
526
527        /// Short summary what [`Operation`] does.
528        ///
529        /// When used with derive [`#[utoipa::path(...)]`][derive_path] attribute macro the value
530        /// is taken from **first line** of doc comment.
531        ///
532        /// [derive_path]: ../../attr.path.html
533        #[serde(skip_serializing_if = "Option::is_none")]
534        pub summary: Option<String>,
535
536        /// Long explanation of [`Operation`] behaviour. Markdown syntax is supported.
537        ///
538        /// When used with derive [`#[utoipa::path(...)]`][derive_path] attribute macro the
539        /// doc comment is used as value for description.
540        ///
541        /// [derive_path]: ../../attr.path.html
542        #[serde(skip_serializing_if = "Option::is_none")]
543        pub description: Option<String>,
544
545        /// Unique identifier for the API [`Operation`]. Most typically this is mapped to handler function name.
546        ///
547        /// When used with derive [`#[utoipa::path(...)]`][derive_path] attribute macro the handler function
548        /// name will be used by default.
549        ///
550        /// [derive_path]: ../../attr.path.html
551        #[serde(skip_serializing_if = "Option::is_none")]
552        pub operation_id: Option<String>,
553
554        /// Additional external documentation for this operation.
555        #[serde(skip_serializing_if = "Option::is_none")]
556        pub external_docs: Option<ExternalDocs>,
557
558        /// List of applicable parameters for this [`Operation`].
559        #[serde(skip_serializing_if = "Option::is_none")]
560        pub parameters: Option<Vec<RefOr<Parameter>>>,
561
562        /// Optional request body for this [`Operation`].
563        #[serde(skip_serializing_if = "Option::is_none")]
564        pub request_body: Option<RefOr<RequestBody>>,
565
566        /// List of possible responses returned by the [`Operation`].
567        pub responses: Responses,
568
569        /// Map of callbacks related to this operation.
570        #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
571        pub callbacks: BTreeMap<String, RefOr<Callback>>,
572
573        /// Define whether the operation is deprecated or not and thus should be avoided consuming.
574        #[serde(skip_serializing_if = "Option::is_none")]
575        pub deprecated: Option<Deprecated>,
576
577        /// Declaration which security mechanisms can be used for for the operation. Only one
578        /// [`SecurityRequirement`] must be met.
579        ///
580        /// Security for the [`Operation`] can be set to optional by adding empty security with
581        /// [`SecurityRequirement::default`].
582        #[serde(skip_serializing_if = "Option::is_none")]
583        pub security: Option<Vec<SecurityRequirement>>,
584
585        /// Alternative [`Server`]s for this [`Operation`].
586        #[serde(skip_serializing_if = "Option::is_none")]
587        pub servers: Option<Vec<Server>>,
588
589        /// Optional extensions "x-something".
590        #[serde(skip_serializing_if = "Option::is_none", flatten)]
591        pub extensions: Option<Extensions>,
592    }
593}
594
595impl Operation {
596    /// Construct a new API [`Operation`].
597    pub fn new() -> Self {
598        Default::default()
599    }
600}
601
602impl OperationBuilder {
603    /// Add or change tags of the [`Operation`].
604    pub fn tags<I: IntoIterator<Item = V>, V: Into<String>>(mut self, tags: Option<I>) -> Self {
605        set_value!(self tags tags.map(|tags| tags.into_iter().map(Into::into).collect()))
606    }
607
608    /// Append tag to [`Operation`] tags.
609    pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
610        let tag_string = tag.into();
611        match self.tags {
612            Some(ref mut tags) => tags.push(tag_string),
613            None => {
614                self.tags = Some(vec![tag_string]);
615            }
616        }
617
618        self
619    }
620
621    /// Add or change short summary of the [`Operation`].
622    pub fn summary<S: Into<String>>(mut self, summary: Option<S>) -> Self {
623        set_value!(self summary summary.map(|summary| summary.into()))
624    }
625
626    /// Add or change description of the [`Operation`].
627    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
628        set_value!(self description description.map(|description| description.into()))
629    }
630
631    /// Add or change operation id of the [`Operation`].
632    pub fn operation_id<S: Into<String>>(mut self, operation_id: Option<S>) -> Self {
633        set_value!(self operation_id operation_id.map(|operation_id| operation_id.into()))
634    }
635
636    /// Add or change parameters of the [`Operation`].
637    pub fn parameters<I: IntoIterator<Item = P>, P: Into<Parameter>>(
638        mut self,
639        parameters: Option<I>,
640    ) -> Self {
641        self.parameters = parameters.map(|parameters| {
642            if let Some(mut params) = self.parameters {
643                params.extend(
644                    parameters
645                        .into_iter()
646                        .map(|parameter| RefOr::T(parameter.into())),
647                );
648                params
649            } else {
650                parameters
651                    .into_iter()
652                    .map(|parameter| RefOr::T(parameter.into()))
653                    .collect()
654            }
655        });
656
657        self
658    }
659
660    /// Append parameter to [`Operation`] parameters.
661    pub fn parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
662        match self.parameters {
663            Some(ref mut parameters) => parameters.push(RefOr::T(parameter.into())),
664            None => {
665                self.parameters = Some(vec![RefOr::T(parameter.into())]);
666            }
667        }
668
669        self
670    }
671
672    /// Append parameter reference to [`Operation`] parameters.
673    pub fn parameter_ref(mut self, parameter: Ref) -> Self {
674        match self.parameters {
675            Some(ref mut parameters) => parameters.push(RefOr::Ref(parameter)),
676            None => {
677                self.parameters = Some(vec![RefOr::Ref(parameter)]);
678            }
679        }
680
681        self
682    }
683
684    /// Add or change request body of the [`Operation`].
685    pub fn request_body(mut self, request_body: Option<RequestBody>) -> Self {
686        set_value!(self request_body request_body.map(Into::into))
687    }
688
689    /// Add or change request body reference of the [`Operation`].
690    pub fn request_body_ref(mut self, request_body: Option<Ref>) -> Self {
691        set_value!(self request_body request_body.map(RefOr::Ref))
692    }
693
694    /// Add or change responses of the [`Operation`].
695    pub fn responses<R: Into<Responses>>(mut self, responses: R) -> Self {
696        set_value!(self responses responses.into())
697    }
698
699    /// Append status code and a [`Response`] to the [`Operation`] responses map.
700    ///
701    /// * `code` must be valid HTTP status code.
702    /// * `response` is instances of [`Response`].
703    pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
704        mut self,
705        code: S,
706        response: R,
707    ) -> Self {
708        self.responses
709            .responses
710            .insert(code.into(), response.into());
711
712        self
713    }
714
715    /// Add a callback to this [`Operation`].
716    pub fn callback<S: Into<String>, C: Into<RefOr<Callback>>>(
717        mut self,
718        name: S,
719        callback: C,
720    ) -> Self {
721        self.callbacks.insert(name.into(), callback.into());
722        self
723    }
724
725    /// Add callbacks to this [`Operation`] from an iterator.
726    pub fn callbacks_from_iter<
727        I: IntoIterator<Item = (S, C)>,
728        S: Into<String>,
729        C: Into<RefOr<Callback>>,
730    >(
731        mut self,
732        callbacks: I,
733    ) -> Self {
734        self.callbacks.extend(
735            callbacks
736                .into_iter()
737                .map(|(name, callback)| (name.into(), callback.into())),
738        );
739        self
740    }
741
742    /// Add or change deprecated status of the [`Operation`].
743    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
744        set_value!(self deprecated deprecated)
745    }
746
747    /// Add or change list of [`SecurityRequirement`]s that are available for [`Operation`].
748    pub fn securities<I: IntoIterator<Item = SecurityRequirement>>(
749        mut self,
750        securities: Option<I>,
751    ) -> Self {
752        set_value!(self security securities.map(|securities| securities.into_iter().collect()))
753    }
754
755    /// Append [`SecurityRequirement`] to [`Operation`] security requirements.
756    pub fn security(mut self, security: SecurityRequirement) -> Self {
757        if let Some(ref mut securities) = self.security {
758            securities.push(security);
759        } else {
760            self.security = Some(vec![security]);
761        }
762
763        self
764    }
765
766    /// Add or change list of [`Server`]s of the [`Operation`].
767    pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
768        set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
769    }
770
771    /// Append a new [`Server`] to the [`Operation`] servers.
772    pub fn server(mut self, server: Server) -> Self {
773        if let Some(ref mut servers) = self.servers {
774            servers.push(server);
775        } else {
776            self.servers = Some(vec![server]);
777        }
778
779        self
780    }
781
782    /// Add openapi extensions (x-something) of the [`Operation`].
783    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
784        set_value!(self extensions extensions)
785    }
786}
787
788impl From<Ref> for RefOr<PathItem> {
789    fn from(r: Ref) -> Self {
790        Self::Ref(r)
791    }
792}
793
794impl From<Ref> for RefOr<Callback> {
795    fn from(r: Ref) -> Self {
796        Self::Ref(r)
797    }
798}
799
800builder! {
801    ParameterBuilder;
802
803    /// Implements [OpenAPI Parameter Object][parameter] for [`Operation`].
804    ///
805    /// [parameter]: https://spec.openapis.org/oas/latest.html#parameter-object
806    #[non_exhaustive]
807    #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
808    #[cfg_attr(feature = "debug", derive(Debug))]
809    #[serde(rename_all = "camelCase")]
810    pub struct Parameter {
811        /// Name of the parameter.
812        ///
813        /// * For [`ParameterIn::Path`] this must in accordance to path templating.
814        /// * For [`ParameterIn::Query`] `Content-Type` or `Authorization` value will be ignored.
815        pub name: String,
816
817        /// Parameter location.
818        #[serde(rename = "in")]
819        pub parameter_in: ParameterIn,
820
821        /// Markdown supported description of the parameter.
822        #[serde(skip_serializing_if = "Option::is_none")]
823        pub description: Option<String>,
824
825        /// Declares whether the parameter is required or not for api.
826        ///
827        /// * For [`ParameterIn::Path`] this must and will be [`Required::True`].
828        pub required: Required,
829
830        /// Declares the parameter deprecated status.
831        #[serde(skip_serializing_if = "Option::is_none")]
832        pub deprecated: Option<Deprecated>,
833        // pub allow_empty_value: bool, this is going to be removed from further open api spec releases
834        /// Schema of the parameter. Typically [`Schema::Object`] is used.
835        #[serde(skip_serializing_if = "Option::is_none")]
836        pub schema: Option<RefOr<Schema>>,
837
838        /// Map of media type representations for the parameter.
839        #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
840        pub content: BTreeMap<String, RefOr<Content>>,
841
842        /// Describes how [`Parameter`] is being serialized depending on [`Parameter::schema`] (type of a content).
843        /// Default value is based on [`ParameterIn`].
844        #[serde(skip_serializing_if = "Option::is_none")]
845        pub style: Option<ParameterStyle>,
846
847        /// When _`true`_ it will generate separate parameter value for each parameter with _`array`_ and _`object`_ type.
848        /// This is also _`true`_ by default for [`ParameterStyle::Form`].
849        ///
850        /// With explode _`false`_:
851        /// ```text
852        /// color=blue,black,brown
853        /// ```
854        ///
855        /// With explode _`true`_:
856        /// ```text
857        /// color=blue&color=black&color=brown
858        /// ```
859        #[serde(skip_serializing_if = "Option::is_none")]
860        pub explode: Option<bool>,
861
862        /// Defines whether parameter should allow reserved characters defined by
863        /// [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2) _`:/?#[]@!$&'()*+,;=`_.
864        /// This is only applicable with [`ParameterIn::Query`]. Default value is _`false`_.
865        #[serde(skip_serializing_if = "Option::is_none")]
866        pub allow_reserved: Option<bool>,
867
868        /// Example of [`Parameter`]'s potential value. This examples will override example
869        /// within [`Parameter::schema`] if defined.
870        #[serde(skip_serializing_if = "Option::is_none")]
871        example: Option<Value>,
872
873        /// Optional extensions "x-something".
874        #[serde(skip_serializing_if = "Option::is_none", flatten)]
875        pub extensions: Option<Extensions>,
876    }
877}
878
879impl Parameter {
880    /// Constructs a new required [`Parameter`] with given name.
881    pub fn new<S: Into<String>>(name: S) -> Self {
882        Self {
883            name: name.into(),
884            required: Required::True,
885            ..Default::default()
886        }
887    }
888}
889
890impl ParameterBuilder {
891    /// Add name of the [`Parameter`].
892    pub fn name<I: Into<String>>(mut self, name: I) -> Self {
893        set_value!(self name name.into())
894    }
895
896    /// Add in of the [`Parameter`].
897    pub fn parameter_in(mut self, parameter_in: ParameterIn) -> Self {
898        set_value!(self parameter_in parameter_in)
899    }
900
901    /// Add required declaration of the [`Parameter`]. If [`ParameterIn::Path`] is
902    /// defined this is always [`Required::True`].
903    pub fn required(mut self, required: Required) -> Self {
904        self.required = required;
905        // required must be true, if parameter_in is Path
906        if self.parameter_in == ParameterIn::Path {
907            self.required = Required::True;
908        }
909
910        self
911    }
912
913    /// Add or change description of the [`Parameter`].
914    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
915        set_value!(self description description.map(|description| description.into()))
916    }
917
918    /// Add or change [`Parameter`] deprecated declaration.
919    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
920        set_value!(self deprecated deprecated)
921    }
922
923    /// Add or change [`Parameter`]s schema.
924    pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: Option<I>) -> Self {
925        set_value!(self schema component.map(|component| component.into()))
926    }
927
928    /// Add media type representation for the [`Parameter`].
929    pub fn content<S: Into<String>, C: Into<RefOr<Content>>>(
930        mut self,
931        content_type: S,
932        content: C,
933    ) -> Self {
934        self.content.insert(content_type.into(), content.into());
935        self
936    }
937
938    /// Add media type representations from an iterator.
939    pub fn contents_from_iter<
940        I: IntoIterator<Item = (S, C)>,
941        S: Into<String>,
942        C: Into<RefOr<Content>>,
943    >(
944        mut self,
945        content: I,
946    ) -> Self {
947        self.content.extend(
948            content
949                .into_iter()
950                .map(|(content_type, content)| (content_type.into(), content.into())),
951        );
952        self
953    }
954
955    /// Add or change serialization style of [`Parameter`].
956    pub fn style(mut self, style: Option<ParameterStyle>) -> Self {
957        set_value!(self style style)
958    }
959
960    /// Define whether [`Parameter`]s are exploded or not.
961    pub fn explode(mut self, explode: Option<bool>) -> Self {
962        set_value!(self explode explode)
963    }
964
965    /// Add or change whether [`Parameter`] should allow reserved characters.
966    pub fn allow_reserved(mut self, allow_reserved: Option<bool>) -> Self {
967        set_value!(self allow_reserved allow_reserved)
968    }
969
970    /// Add or change example of [`Parameter`]'s potential value.
971    pub fn example(mut self, example: Option<Value>) -> Self {
972        set_value!(self example example)
973    }
974
975    /// Add openapi extensions (x-something) to the [`Parameter`].
976    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
977        set_value!(self extensions extensions)
978    }
979}
980
981/// In definition of [`Parameter`].
982#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
983#[serde(rename_all = "lowercase")]
984#[cfg_attr(feature = "debug", derive(Debug))]
985pub enum ParameterIn {
986    /// Declares that parameter is used as query parameter.
987    Query,
988    /// Declares that parameter is used as path parameter.
989    Path,
990    /// Declares that parameter is used as header value.
991    Header,
992    /// Declares that parameter is used as cookie value.
993    Cookie,
994    /// Declares that parameter represents the entire query string.
995    #[serde(rename = "querystring")]
996    QueryString,
997}
998
999impl Default for ParameterIn {
1000    fn default() -> Self {
1001        Self::Path
1002    }
1003}
1004
1005/// Defines how [`Parameter`] should be serialized.
1006#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
1007#[cfg_attr(feature = "debug", derive(Debug))]
1008#[serde(rename_all = "camelCase")]
1009pub enum ParameterStyle {
1010    /// Path style parameters defined by [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.7)
1011    /// e.g _`;color=blue`_.
1012    /// Allowed with [`ParameterIn::Path`].
1013    Matrix,
1014    /// Label style parameters defined by [RFC6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.5)
1015    /// e.g _`.color=blue`_.
1016    /// Allowed with [`ParameterIn::Path`].
1017    Label,
1018    /// Form style parameters defined by [RFC6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8)
1019    /// e.g. _`color=blue`_. Default value for [`ParameterIn::Query`] [`ParameterIn::Cookie`].
1020    /// Allowed with [`ParameterIn::Query`] or [`ParameterIn::Cookie`].
1021    Form,
1022    /// Default value for [`ParameterIn::Path`] [`ParameterIn::Header`]. e.g. _`blue`_.
1023    /// Allowed with [`ParameterIn::Path`] or [`ParameterIn::Header`].
1024    Simple,
1025    /// Space separated array values e.g. _`blue%20black%20brown`_.
1026    /// Allowed with [`ParameterIn::Query`].
1027    SpaceDelimited,
1028    /// Pipe separated array values e.g. _`blue|black|brown`_.
1029    /// Allowed with [`ParameterIn::Query`].
1030    PipeDelimited,
1031    /// Simple way of rendering nested objects using form parameters .e.g. _`color[B]=150`_.
1032    /// Allowed with [`ParameterIn::Query`].
1033    DeepObject,
1034    /// Cookie parameter serialization style.
1035    Cookie,
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::{HttpMethod, Operation, OperationBuilder};
1041    use crate::openapi::{security::SecurityRequirement, server::Server, PathItem, PathsBuilder};
1042
1043    #[test]
1044    fn test_path_order() {
1045        let paths_list = PathsBuilder::new()
1046            .path(
1047                "/todo",
1048                PathItem::new(HttpMethod::Get, OperationBuilder::new()),
1049            )
1050            .path(
1051                "/todo",
1052                PathItem::new(HttpMethod::Post, OperationBuilder::new()),
1053            )
1054            .path(
1055                "/todo/{id}",
1056                PathItem::new(HttpMethod::Delete, OperationBuilder::new()),
1057            )
1058            .path(
1059                "/todo/{id}",
1060                PathItem::new(HttpMethod::Get, OperationBuilder::new()),
1061            )
1062            .path(
1063                "/todo/{id}",
1064                PathItem::new(HttpMethod::Put, OperationBuilder::new()),
1065            )
1066            .path(
1067                "/todo/search",
1068                PathItem::new(HttpMethod::Get, OperationBuilder::new()),
1069            )
1070            .build();
1071
1072        let actual_value = paths_list
1073            .paths
1074            .iter()
1075            .flat_map(|(path, path_item)| {
1076                let mut path_methods =
1077                    Vec::<(&str, &HttpMethod)>::with_capacity(paths_list.paths.len());
1078                if path_item.get.is_some() {
1079                    path_methods.push((path, &HttpMethod::Get));
1080                }
1081                if path_item.put.is_some() {
1082                    path_methods.push((path, &HttpMethod::Put));
1083                }
1084                if path_item.post.is_some() {
1085                    path_methods.push((path, &HttpMethod::Post));
1086                }
1087                if path_item.delete.is_some() {
1088                    path_methods.push((path, &HttpMethod::Delete));
1089                }
1090                if path_item.options.is_some() {
1091                    path_methods.push((path, &HttpMethod::Options));
1092                }
1093                if path_item.head.is_some() {
1094                    path_methods.push((path, &HttpMethod::Head));
1095                }
1096                if path_item.patch.is_some() {
1097                    path_methods.push((path, &HttpMethod::Patch));
1098                }
1099                if path_item.trace.is_some() {
1100                    path_methods.push((path, &HttpMethod::Trace));
1101                }
1102
1103                path_methods
1104            })
1105            .collect::<Vec<_>>();
1106
1107        let get = HttpMethod::Get;
1108        let post = HttpMethod::Post;
1109        let put = HttpMethod::Put;
1110        let delete = HttpMethod::Delete;
1111
1112        #[cfg(not(feature = "preserve_path_order"))]
1113        {
1114            let expected_value = vec![
1115                ("/todo", &get),
1116                ("/todo", &post),
1117                ("/todo/search", &get),
1118                ("/todo/{id}", &get),
1119                ("/todo/{id}", &put),
1120                ("/todo/{id}", &delete),
1121            ];
1122            assert_eq!(actual_value, expected_value);
1123        }
1124
1125        #[cfg(feature = "preserve_path_order")]
1126        {
1127            let expected_value = vec![
1128                ("/todo", &get),
1129                ("/todo", &post),
1130                ("/todo/{id}", &get),
1131                ("/todo/{id}", &put),
1132                ("/todo/{id}", &delete),
1133                ("/todo/search", &get),
1134            ];
1135            assert_eq!(actual_value, expected_value);
1136        }
1137    }
1138
1139    #[test]
1140    fn operation_new() {
1141        let operation = Operation::new();
1142
1143        assert!(operation.tags.is_none());
1144        assert!(operation.summary.is_none());
1145        assert!(operation.description.is_none());
1146        assert!(operation.operation_id.is_none());
1147        assert!(operation.external_docs.is_none());
1148        assert!(operation.parameters.is_none());
1149        assert!(operation.request_body.is_none());
1150        assert!(operation.responses.responses.is_empty());
1151        assert!(operation.callbacks.is_empty());
1152        assert!(operation.deprecated.is_none());
1153        assert!(operation.security.is_none());
1154        assert!(operation.servers.is_none());
1155    }
1156
1157    #[test]
1158    fn operation_builder_security() {
1159        let security_requirement1 =
1160            SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
1161        let security_requirement2 = SecurityRequirement::new("api_oauth2_flow", ["remove:items"]);
1162        let operation = OperationBuilder::new()
1163            .security(security_requirement1)
1164            .security(security_requirement2)
1165            .build();
1166
1167        assert!(operation.security.is_some());
1168    }
1169
1170    #[test]
1171    fn operation_builder_server() {
1172        let server1 = Server::new("/api");
1173        let server2 = Server::new("/admin");
1174        let operation = OperationBuilder::new()
1175            .server(server1)
1176            .server(server2)
1177            .build();
1178
1179        assert!(operation.servers.is_some());
1180    }
1181}