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};
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
31    /// [`RequestBody::default`].
32    #[must_use]
33    pub fn new() -> Self {
34        Default::default()
35    }
36    /// Add description for [`RequestBody`].
37    #[must_use]
38    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
39        self.description = Some(description.into());
40        self
41    }
42
43    /// Define [`RequestBody`] required.
44    #[must_use]
45    pub fn required(mut self, required: Required) -> Self {
46        self.required = Some(required);
47        self
48    }
49
50    /// Add [`Content`] by content type e.g `application/json` to [`RequestBody`].
51    #[must_use]
52    pub fn add_content<S: Into<String>, C: Into<Content>>(mut self, kind: S, content: C) -> Self {
53        self.contents.insert(kind.into(), content.into());
54        self
55    }
56
57    /// Fill [`RequestBody`] with values from another [`RequestBody`].
58    pub fn merge(&mut self, other: Self) {
59        let Self {
60            description,
61            contents,
62            required,
63        } = other;
64        if let Some(description) = description
65            && !description.is_empty()
66        {
67            self.description = Some(description);
68        }
69        self.contents.extend(contents);
70        if let Some(required) = required {
71            self.required = Some(required);
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use assert_json_diff::assert_json_eq;
79    use serde_json::json;
80
81    use super::{Content, RequestBody, Required};
82
83    #[test]
84    fn request_body_new() {
85        let request_body = RequestBody::new();
86
87        assert!(request_body.contents.is_empty());
88        assert_eq!(request_body.description, None);
89        assert!(request_body.required.is_none());
90    }
91
92    #[test]
93    fn request_body_builder() -> Result<(), serde_json::Error> {
94        let request_body = RequestBody::new()
95            .description("A sample requestBody")
96            .required(Required::True)
97            .add_content(
98                "application/json",
99                Content::new(crate::Ref::from_schema_name("EmailPayload")),
100            );
101
102        assert_json_eq!(
103            request_body,
104            json!({
105              "description": "A sample requestBody",
106              "content": {
107                "application/json": {
108                  "schema": {
109                    "$ref": "#/components/schemas/EmailPayload"
110                  }
111                }
112              },
113              "required": true
114            })
115        );
116        Ok(())
117    }
118
119    #[test]
120    fn request_body_merge() {
121        let mut request_body = RequestBody::new();
122        let other_request_body = RequestBody::new()
123            .description("Merged requestBody")
124            .required(Required::True)
125            .add_content(
126                "application/json",
127                Content::new(crate::Ref::from_schema_name("EmailPayload")),
128            );
129
130        request_body.merge(other_request_body);
131        assert_json_eq!(
132            request_body,
133            json!({
134              "description": "Merged requestBody",
135              "content": {
136                "application/json": {
137                  "schema": {
138                    "$ref": "#/components/schemas/EmailPayload"
139                  }
140                }
141              },
142              "required": true
143            })
144        );
145    }
146}