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