salvo_oapi/openapi/
request_body.rs

1//! Implements [OpenAPI Request Body][request_body] types.
2//!
3//! [request_body]: https://spec.openapis.org/oas/latest.html#request-body-object
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use super::{Content, Required};
8
9/// Implements [OpenAPI Request Body][request_body].
10///
11/// [request_body]: https://spec.openapis.org/oas/latest.html#request-body-object
12#[non_exhaustive]
13#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
14#[serde(rename_all = "camelCase")]
15pub struct RequestBody {
16    /// Additional description of [`RequestBody`] supporting markdown syntax.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub description: Option<String>,
19
20    /// Map of request body contents mapped by content type e.g. `application/json`.
21    #[serde(rename = "content")]
22    pub contents: IndexMap<String, Content>,
23
24    /// Determines whether request body is required in the request or not.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub required: Option<Required>,
27}
28
29impl RequestBody {
30    /// Construct a new empty [`RequestBody`]. This is effectively same as calling [`RequestBody::default`].
31    pub fn new() -> Self {
32        Default::default()
33    }
34    /// Add description for [`RequestBody`].
35    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
36        self.description = Some(description.into());
37        self
38    }
39
40    /// Define [`RequestBody`] required.
41    pub fn required(mut self, required: Required) -> Self {
42        self.required = Some(required);
43        self
44    }
45
46    /// Add [`Content`] by content type e.g `application/json` to [`RequestBody`].
47    pub fn add_content<S: Into<String>, C: Into<Content>>(mut self, kind: S, content: C) -> Self {
48        self.contents.insert(kind.into(), content.into());
49        self
50    }
51
52    /// Fill [`RequestBody`] with values from another [`RequestBody`].
53    pub fn merge(&mut self, other: RequestBody) {
54        let RequestBody {
55            description,
56            contents,
57            required,
58        } = other;
59        if let Some(description) = description {
60            if !description.is_empty() {
61                self.description = Some(description);
62            }
63        }
64        self.contents.extend(contents);
65        if let Some(required) = required {
66            self.required = Some(required);
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use assert_json_diff::assert_json_eq;
74    use serde_json::json;
75
76    use super::{Content, RequestBody, Required};
77
78    #[test]
79    fn request_body_new() {
80        let request_body = RequestBody::new();
81
82        assert!(request_body.contents.is_empty());
83        assert_eq!(request_body.description, None);
84        assert!(request_body.required.is_none());
85    }
86
87    #[test]
88    fn request_body_builder() -> Result<(), serde_json::Error> {
89        let request_body = RequestBody::new()
90            .description("A sample requestBody")
91            .required(Required::True)
92            .add_content(
93                "application/json",
94                Content::new(crate::Ref::from_schema_name("EmailPayload")),
95            );
96
97        assert_json_eq!(
98            request_body,
99            json!({
100              "description": "A sample requestBody",
101              "content": {
102                "application/json": {
103                  "schema": {
104                    "$ref": "#/components/schemas/EmailPayload"
105                  }
106                }
107              },
108              "required": true
109            })
110        );
111        Ok(())
112    }
113
114    #[test]
115    fn request_body_merge() {
116        let mut request_body = RequestBody::new();
117        let other_request_body = RequestBody::new()
118            .description("Merged requestBody")
119            .required(Required::True)
120            .add_content(
121                "application/json",
122                Content::new(crate::Ref::from_schema_name("EmailPayload")),
123            );
124
125        request_body.merge(other_request_body);
126        assert_json_eq!(
127            request_body,
128            json!({
129              "description": "Merged requestBody",
130              "content": {
131                "application/json": {
132                  "schema": {
133                    "$ref": "#/components/schemas/EmailPayload"
134                  }
135                }
136              },
137              "required": true
138            })
139        );
140    }
141}