1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Implements [OpenAPI Request Body][request_body] types.
//!
//! [request_body]: https://spec.openapis.org/oas/latest.html#request-body-object
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

use super::{Content, Required};

/// Implements [OpenAPI Request Body][request_body].
///
/// [request_body]: https://spec.openapis.org/oas/latest.html#request-body-object
#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RequestBody {
    /// Additional description of [`RequestBody`] supporting markdown syntax.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    /// Map of request body contents mapped by content type e.g. `application/json`.
    #[serde(rename = "content")]
    pub contents: IndexMap<String, Content>,

    /// Determines whether request body is required in the request or not.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<Required>,
}

impl RequestBody {
    /// Construct a new empty [`RequestBody`]. This is effectively same as calling [`RequestBody::default`].
    pub fn new() -> Self {
        Default::default()
    }
    /// Add description for [`RequestBody`].
    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Define [`RequestBody`] required.
    pub fn required(mut self, required: Required) -> Self {
        self.required = Some(required);
        self
    }

    /// Add [`Content`] by content type e.g `application/json` to [`RequestBody`].
    pub fn add_content<S: Into<String>, C: Into<Content>>(mut self, kind: S, content: C) -> Self {
        self.contents.insert(kind.into(), content.into());
        self
    }

    /// Fill [`RequestBody`] with values from another [`RequestBody`].
    pub fn merge(&mut self, other: RequestBody) {
        let RequestBody {
            description,
            contents,
            required,
        } = other;
        if let Some(description) = description {
            if !description.is_empty() {
                self.description = Some(description);
            }
        }
        self.contents.extend(contents);
        if let Some(required) = required {
            self.required = Some(required);
        }
    }
}

#[cfg(test)]
mod tests {
    use assert_json_diff::assert_json_eq;
    use serde_json::json;

    use super::{Content, RequestBody, Required};

    #[test]
    fn request_body_new() {
        let request_body = RequestBody::new();

        assert!(request_body.contents.is_empty());
        assert_eq!(request_body.description, None);
        assert!(request_body.required.is_none());
    }

    #[test]
    fn request_body_builder() -> Result<(), serde_json::Error> {
        let request_body = RequestBody::new()
            .description("A sample requestBody")
            .required(Required::True)
            .add_content(
                "application/json",
                Content::new(crate::Ref::from_schema_name("EmailPayload")),
            );

        assert_json_eq!(
            request_body,
            json!({
              "description": "A sample requestBody",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/EmailPayload"
                  }
                }
              },
              "required": true
            })
        );
        Ok(())
    }

    #[test]
    fn request_body_merge() {
        let mut request_body = RequestBody::new();
        let other_request_body = RequestBody::new()
            .description("Merged requestBody")
            .required(Required::True)
            .add_content(
                "application/json",
                Content::new(crate::Ref::from_schema_name("EmailPayload")),
            );

        request_body.merge(other_request_body);
        assert_json_eq!(
            request_body,
            json!({
              "description": "Merged requestBody",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/EmailPayload"
                  }
                }
              },
              "required": true
            })
        );
    }
}