salvo_oapi/openapi/
parameter.rs

1//! Implements [OpenAPI Parameter Object][parameter] types.
2//!
3//! [parameter]: https://spec.openapis.org/oas/latest.html#parameter-object
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use super::{Deprecated, RefOr, Required, Schema};
8use crate::PropMap;
9
10/// Collection for OpenAPI Parameter Objects.
11#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)]
12pub struct Parameters(pub Vec<Parameter>);
13
14impl IntoIterator for Parameters {
15    type Item = Parameter;
16    type IntoIter = <Vec<Parameter> as IntoIterator>::IntoIter;
17
18    fn into_iter(self) -> Self::IntoIter {
19        self.0.into_iter()
20    }
21}
22
23impl Parameters {
24    /// Construct a new empty [`Parameters`]. This is effectively same as calling [`Parameters::default`].
25    #[must_use]
26    pub fn new() -> Self {
27        Default::default()
28    }
29    /// Returns `true` if instance contains no elements.
30    #[must_use]
31    pub fn is_empty(&self) -> bool {
32        self.0.is_empty()
33    }
34    /// Add a new parameter and returns `self`.
35    #[must_use]
36    pub fn parameter<P: Into<Parameter>>(mut self, parameter: P) -> Self {
37        self.insert(parameter);
38        self
39    }
40    /// Returns `true` if instance contains a parameter with the given name and location.
41    #[must_use]
42    pub fn contains(&self, name: &str, parameter_in: ParameterIn) -> bool {
43        self.0
44            .iter()
45            .any(|item| item.name == name && item.parameter_in == parameter_in)
46    }
47    /// Inserts a parameter into the instance.
48    pub fn insert<P: Into<Parameter>>(&mut self, parameter: P) {
49        let parameter = parameter.into();
50        let exist_item = self.0.iter_mut().find(|item| {
51            item.name == parameter.name && item.parameter_in == parameter.parameter_in
52        });
53
54        if let Some(exist_item) = exist_item {
55            exist_item.merge(parameter);
56        } else {
57            self.0.push(parameter);
58        }
59    }
60    /// Moves all elements from `other` into `self`, leaving `other` empty.
61    ///
62    /// If a key from `other` is already present in `self`, the respective
63    /// value from `self` will be overwritten with the respective value from `other`.
64    pub fn append(&mut self, other: &mut Self) {
65        for item in other.0.drain(..) {
66            self.insert(item);
67        }
68    }
69    /// Extends a collection with the contents of an iterator.
70    pub fn extend<I>(&mut self, iter: I)
71    where
72        I: IntoIterator<Item = Parameter>,
73    {
74        for item in iter {
75            self.insert(item);
76        }
77    }
78}
79
80/// Implements [OpenAPI Parameter Object][parameter] for [`Operation`](struct.Operation).
81///
82/// [parameter]: https://spec.openapis.org/oas/latest.html#parameter-object
83#[non_exhaustive]
84#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
85#[serde(rename_all = "camelCase")]
86pub struct Parameter {
87    /// Name of the parameter.
88    ///
89    /// * For [`ParameterIn::Path`] this must in accordance to path templating.
90    /// * For [`ParameterIn::Query`] `Content-Type` or `Authorization` value will be ignored.
91    pub name: String,
92
93    /// Parameter location.
94    #[serde(rename = "in")]
95    pub parameter_in: ParameterIn,
96
97    /// Markdown supported description of the parameter.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub description: Option<String>,
100
101    /// Declares whether the parameter is required or not for api.
102    ///
103    /// * For [`ParameterIn::Path`] this must and will be [`Required::True`].
104    pub required: Required,
105
106    /// Declares the parameter deprecated status.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub deprecated: Option<Deprecated>,
109    // pub allow_empty_value: bool, this is going to be removed from further open api spec releases
110    /// Schema of the parameter. Typically [`Schema::Object`] is used.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub schema: Option<RefOr<Schema>>,
113
114    /// Describes how [`Parameter`] is being serialized depending on [`Parameter::schema`] (type of a content).
115    /// Default value is based on [`ParameterIn`].
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub style: Option<ParameterStyle>,
118
119    /// When _`true`_ it will generate separate parameter value for each parameter with _`array`_ and _`object`_ type.
120    /// This is also _`true`_ by default for [`ParameterStyle::Form`].
121    ///
122    /// With explode _`false`_:
123    /// ```text
124    ///color=blue,black,brown
125    /// ```
126    ///
127    /// With explode _`true`_:
128    /// ```text
129    ///color=blue&color=black&color=brown
130    /// ```
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub explode: Option<bool>,
133
134    /// Defines whether parameter should allow reserved characters defined by
135    /// [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2) _`:/?#[]@!$&'()*+,;=`_.
136    /// This is only applicable with [`ParameterIn::Query`]. Default value is _`false`_.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub allow_reserved: Option<bool>,
139
140    /// Example of [`Parameter`]'s potential value. This examples will override example
141    /// within [`Parameter::schema`] if defined.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    example: Option<Value>,
144
145    /// Optional extensions "x-something"
146    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
147    pub extensions: PropMap<String, serde_json::Value>,
148}
149
150impl Parameter {
151    /// Constructs a new required [`Parameter`] with given name.
152    #[must_use]
153    pub fn new<S: Into<String>>(name: S) -> Self {
154        Self {
155            name: name.into(),
156            required: Required::Unset,
157            ..Default::default()
158        }
159    }
160    /// Add name of the [`Parameter`].
161    #[must_use]
162    pub fn name<I: Into<String>>(mut self, name: I) -> Self {
163        self.name = name.into();
164        self
165    }
166
167    /// Add in of the [`Parameter`].
168    #[must_use]
169    pub fn parameter_in(mut self, parameter_in: ParameterIn) -> Self {
170        self.parameter_in = parameter_in;
171        if self.parameter_in == ParameterIn::Path {
172            self.required = Required::True;
173        }
174        self
175    }
176
177    /// Fill [`Parameter`] with values from another [`Parameter`]. Fields will replaced if it is not set.
178    pub fn merge(&mut self, other: Self) -> bool {
179        let Self {
180            name,
181            parameter_in,
182            description,
183            required,
184            deprecated,
185            schema,
186            style,
187            explode,
188            allow_reserved,
189            example,
190            extensions,
191        } = other;
192        if name != self.name || parameter_in != self.parameter_in {
193            return false;
194        }
195        if let Some(description) = description {
196            self.description = Some(description);
197        }
198
199        if required != Required::Unset {
200            self.required = required;
201        }
202
203        if let Some(deprecated) = deprecated {
204            self.deprecated = Some(deprecated);
205        }
206        if let Some(schema) = schema {
207            self.schema = Some(schema);
208        }
209        if let Some(style) = style {
210            self.style = Some(style);
211        }
212        if let Some(explode) = explode {
213            self.explode = Some(explode);
214        }
215        if let Some(allow_reserved) = allow_reserved {
216            self.allow_reserved = Some(allow_reserved);
217        }
218        if let Some(example) = example {
219            self.example = Some(example);
220        }
221
222        self.extensions.extend(extensions);
223        true
224    }
225
226    /// Add required declaration of the [`Parameter`]. If [`ParameterIn::Path`] is
227    /// defined this is always [`Required::True`].
228    #[must_use]
229    pub fn required(mut self, required: impl Into<Required>) -> Self {
230        self.required = required.into();
231        // required must be true, if parameter_in is Path
232        if self.parameter_in == ParameterIn::Path {
233            self.required = Required::True;
234        }
235
236        self
237    }
238
239    /// Add or change description of the [`Parameter`].
240    #[must_use]
241    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
242        self.description = Some(description.into());
243        self
244    }
245
246    /// Add or change [`Parameter`] deprecated declaration.
247    #[must_use]
248    pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
249        self.deprecated = Some(deprecated.into());
250        self
251    }
252
253    /// Add or change [`Parameter`]s schema.
254    #[must_use]
255    pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
256        self.schema = Some(component.into());
257        self
258    }
259
260    /// Add or change serialization style of [`Parameter`].
261    #[must_use]
262    pub fn style(mut self, style: ParameterStyle) -> Self {
263        self.style = Some(style);
264        self
265    }
266
267    /// Define whether [`Parameter`]s are exploded or not.
268    #[must_use]
269    pub fn explode(mut self, explode: bool) -> Self {
270        self.explode = Some(explode);
271        self
272    }
273
274    /// Add or change whether [`Parameter`] should allow reserved characters.
275    #[must_use]
276    pub fn allow_reserved(mut self, allow_reserved: bool) -> Self {
277        self.allow_reserved = Some(allow_reserved);
278        self
279    }
280
281    /// Add or change example of [`Parameter`]'s potential value.
282    #[must_use]
283    pub fn example(mut self, example: Value) -> Self {
284        self.example = Some(example);
285        self
286    }
287}
288
289/// In definition of [`Parameter`].
290#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Default, Copy, Debug)]
291#[serde(rename_all = "lowercase")]
292pub enum ParameterIn {
293    /// Declares that parameter is used as query parameter.
294    Query,
295    /// Declares that parameter is used as path parameter.
296    #[default]
297    Path,
298    /// Declares that parameter is used as header value.
299    Header,
300    /// Declares that parameter is used as cookie value.
301    Cookie,
302}
303
304/// Defines how [`Parameter`] should be serialized.
305#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
306#[serde(rename_all = "camelCase")]
307pub enum ParameterStyle {
308    /// Path style parameters defined by [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.7)
309    /// e.g _`;color=blue`_.
310    /// Allowed with [`ParameterIn::Path`].
311    Matrix,
312    /// Label style parameters defined by [RFC6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.5)
313    /// e.g _`.color=blue`_.
314    /// Allowed with [`ParameterIn::Path`].
315    Label,
316    /// Form style parameters defined by [RFC6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8)
317    /// e.g. _`color=blue`_. Default value for [`ParameterIn::Query`] [`ParameterIn::Cookie`].
318    /// Allowed with [`ParameterIn::Query`] or [`ParameterIn::Cookie`].
319    Form,
320    /// Default value for [`ParameterIn::Path`] [`ParameterIn::Header`]. e.g. _`blue`_.
321    /// Allowed with [`ParameterIn::Path`] or [`ParameterIn::Header`].
322    Simple,
323    /// Space separated array values e.g. _`blue%20black%20brown`_.
324    /// Allowed with [`ParameterIn::Query`].
325    SpaceDelimited,
326    /// Pipe separated array values e.g. _`blue|black|brown`_.
327    /// Allowed with [`ParameterIn::Query`].
328    PipeDelimited,
329    /// Simple way of rendering nested objects using form parameters .e.g. _`color[B]=150`_.
330    /// Allowed with [`ParameterIn::Query`].
331    DeepObject,
332}
333
334#[cfg(test)]
335mod tests {
336    use assert_json_diff::assert_json_eq;
337    use serde_json::json;
338
339    use crate::Object;
340
341    use super::*;
342
343    #[test]
344    fn test_build_parameter() {
345        let parameter = Parameter::new("name");
346        assert_eq!(parameter.name, "name");
347
348        let parameter = parameter
349            .name("new name")
350            .parameter_in(ParameterIn::Query)
351            .required(Required::True)
352            .description("description")
353            .deprecated(Deprecated::False)
354            .schema(Schema::object(Object::new()))
355            .style(ParameterStyle::Simple)
356            .explode(true)
357            .allow_reserved(true)
358            .example(Value::String("example".to_owned()));
359        assert_json_eq!(
360            parameter,
361            json!({
362                "name": "new name",
363                "in": "query",
364                "required": true,
365                "description": "description",
366                "deprecated": false,
367                "schema": {
368                    "type": "object"
369                },
370                "style": "simple",
371                "explode": true,
372                "allowReserved": true,
373                "example": "example"
374            })
375        );
376    }
377
378    #[test]
379    fn test_parameter_merge_fail() {
380        let mut parameter1 = Parameter::new("param1");
381        let parameter2 = Parameter::new("param2");
382
383        assert!(!parameter1.merge(parameter2));
384    }
385
386    #[test]
387    fn test_parameter_merge_success() {
388        let mut parameter1 = Parameter::new("param1");
389        let mut parameter2 = Parameter::new("param1")
390            .description("description")
391            .required(Required::True)
392            .deprecated(Deprecated::True)
393            .schema(Schema::object(Object::new()))
394            .style(ParameterStyle::Form)
395            .explode(true)
396            .allow_reserved(true)
397            .example(Value::String("example".to_owned()));
398
399        parameter1.extensions =
400            PropMap::from([("key1".to_owned(), Value::String("value1".to_owned()))]);
401        parameter2.extensions =
402            PropMap::from([("key2".to_owned(), Value::String("value2".to_owned()))]);
403
404        assert!(parameter1.merge(parameter2));
405        assert_json_eq!(
406            parameter1,
407            json!({
408                "name": "param1",
409                "in": "path",
410                "description": "description",
411                "required": true,
412                "deprecated": true,
413                "schema": {
414                    "type": "object"
415                },
416                "style": "form",
417                "explode": true,
418                "allowReserved": true,
419                "example": "example",
420                "key1": "value1",
421                "key2": "value2"
422            })
423        )
424    }
425
426    #[test]
427    fn test_parameter_merge_no_extensions() {
428        let mut parameter1 = Parameter::new("param1");
429        let mut parameter2 = Parameter::new("param1")
430            .description("description")
431            .required(Required::True)
432            .deprecated(Deprecated::True)
433            .schema(Schema::object(Object::new()))
434            .style(ParameterStyle::Form)
435            .explode(true)
436            .allow_reserved(true)
437            .example(Value::String("example".to_owned()));
438
439        parameter2.extensions =
440            PropMap::from([("key2".to_owned(), Value::String("value2".to_owned()))]);
441
442        assert!(parameter1.merge(parameter2));
443        assert_json_eq!(
444            parameter1,
445            json!({
446                "name": "param1",
447                "in": "path",
448                "description": "description",
449                "required": true,
450                "deprecated": true,
451                "schema": {
452                    "type": "object"
453                },
454                "style": "form",
455                "explode": true,
456                "allowReserved": true,
457                "example": "example",
458                "key2": "value2",
459            })
460        )
461    }
462
463    #[test]
464    fn test_build_parameters() {
465        let parameters = Parameters::new();
466        assert!(parameters.is_empty());
467    }
468
469    #[test]
470    fn test_parameters_into_iter() {
471        let parameters = Parameters::new().parameter(Parameter::new("param"));
472        let mut iter = parameters.into_iter();
473        assert_eq!(iter.next(), Some(Parameter::new("param")));
474        assert!(iter.next().is_none());
475    }
476
477    #[test]
478    fn test_parameters_contain() {
479        let parameters = Parameters::new().parameter(Parameter::new("param"));
480        assert!(parameters.contains("param", ParameterIn::Path));
481    }
482
483    #[test]
484    fn test_parameters_insert_existed_item() {
485        let mut parameters = Parameters::new();
486        parameters.insert(Parameter::new("param"));
487        assert!(parameters.contains("param", ParameterIn::Path));
488
489        parameters.insert(Parameter::new("param"));
490        assert_eq!(parameters.0.len(), 1);
491    }
492
493    #[test]
494    fn test_parameters_append() {
495        let mut parameters1 = Parameters::new().parameter(Parameter::new("param1"));
496        let mut parameters2 = Parameters::new().parameter(Parameter::new("param2"));
497
498        parameters1.append(&mut parameters2);
499        assert_json_eq!(
500            parameters1,
501            json!([
502                {
503                    "in": "path",
504                    "name": "param1",
505                    "required": false
506                },
507                {
508                    "in": "path",
509                    "name": "param2",
510                    "required": false
511                }
512            ])
513        );
514    }
515}