openapiv3_1/
path.rs

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