openapiv3_1/
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_derive::{Deserialize, Serialize};
6
7use super::Content;
8use super::extensions::Extensions;
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, PartialEq, bon::Builder)]
15#[cfg_attr(feature = "debug", derive(Debug))]
16#[serde(rename_all = "camelCase")]
17#[builder(on(_, into))]
18pub struct RequestBody {
19    /// Map of request body contents mapped by content type e.g. `application/json`.
20    #[builder(field)]
21    pub content: IndexMap<String, Content>,
22
23    /// Additional description of [`RequestBody`] supporting markdown syntax.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub description: Option<String>,
26
27    /// Determines whether request body is required in the request or not.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub required: Option<bool>,
30
31    /// Optional extensions "x-something".
32    #[serde(skip_serializing_if = "Option::is_none", flatten)]
33    pub extensions: Option<Extensions>,
34}
35
36impl RequestBody {
37    /// Construct a new [`RequestBody`].
38    pub fn new() -> Self {
39        Default::default()
40    }
41}
42
43impl<S: request_body_builder::State> RequestBodyBuilder<S> {
44    /// Add [`Content`] by content type e.g `application/json` to [`RequestBody`].
45    pub fn content(mut self, content_type: impl Into<String>, content: impl Into<Content>) -> Self {
46        self.content.insert(content_type.into(), content.into());
47        self
48    }
49
50    /// Add [`Content`] by content type e.g `application/json` to [`RequestBody`].
51    pub fn contents<T: Into<String>, C: Into<Content>>(self, contents: impl IntoIterator<Item = (T, C)>) -> Self {
52        contents.into_iter().fold(self, |this, (t, c)| this.content(t, c))
53    }
54}
55
56impl<S: request_body_builder::IsComplete> From<RequestBodyBuilder<S>> for RequestBody {
57    fn from(value: RequestBodyBuilder<S>) -> Self {
58        value.build()
59    }
60}
61
62#[cfg(test)]
63#[cfg_attr(coverage_nightly, coverage(off))]
64mod tests {
65    use insta::assert_json_snapshot;
66
67    use super::{Content, RequestBody};
68    use crate::Ref;
69
70    #[test]
71    fn request_body_new() {
72        let request_body = RequestBody::new();
73
74        assert!(request_body.content.is_empty());
75        assert_eq!(request_body.description, None);
76        assert!(request_body.required.is_none());
77    }
78
79    #[test]
80    fn request_body_builder() {
81        let request_body = RequestBody::builder()
82            .description("A sample requestBody")
83            .required(true)
84            .content("application/json", Content::new(Some(Ref::from_schema_name("EmailPayload"))))
85            .build();
86        assert_json_snapshot!(request_body);
87    }
88}