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