Skip to main content

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