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