salvo_oapi/openapi/
content.rs

1//! Implements content object for request body and response.
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::encoding::Encoding;
6use super::example::Example;
7use super::{PropMap, RefOr, Schema};
8
9/// Content holds request body content or response content.
10#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
11#[non_exhaustive]
12pub struct Content {
13    /// Schema used in response body or request body.
14    pub schema: RefOr<Schema>,
15
16    /// Example for request body or response body.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub example: Option<Value>,
19
20    /// Examples of the request body or response body. [`Content::examples`] should match to
21    /// media type and specified schema if present. [`Content::examples`] and
22    /// [`Content::example`] are mutually exclusive. If both are defined `examples` will
23    /// override value in `example`.
24    #[serde(skip_serializing_if = "PropMap::is_empty")]
25    pub examples: PropMap<String, RefOr<Example>>,
26
27    /// A map between a property name and its encoding information.
28    ///
29    /// The key, being the property name, MUST exist in the [`Content::schema`] as a property, with
30    /// `schema` being a [`Schema::Object`] and this object containing the same property key in
31    /// [`Object::properties`](crate::schema::Object::properties).
32    ///
33    /// The encoding object SHALL only apply to `request_body` objects when the media type is
34    /// multipart or `application/x-www-form-urlencoded`.
35    #[serde(skip_serializing_if = "PropMap::is_empty", default)]
36    pub encoding: PropMap<String, Encoding>,
37}
38
39impl Content {
40    /// Construct a new [`Content`].
41    #[must_use]
42    pub fn new<I: Into<RefOr<Schema>>>(schema: I) -> Self {
43        Self {
44            schema: schema.into(),
45            ..Self::default()
46        }
47    }
48
49    /// Add schema.
50    #[must_use]
51    pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
52        self.schema = component.into();
53        self
54    }
55
56    /// Add example of schema.
57    #[must_use]
58    pub fn example(mut self, example: Value) -> Self {
59        self.example = Some(example);
60        self
61    }
62
63    /// Add iterator of _`(N, V)`_ where `N` is name of example and `V` is [`Example`][example] to
64    /// [`Content`] of a request body or response body.
65    ///
66    /// [`Content::examples`] and [`Content::example`] are mutually exclusive. If both are defined
67    /// `examples` will override value in `example`.
68    ///
69    /// [example]: ../example/Example.html
70    #[must_use]
71    pub fn extend_examples<
72        E: IntoIterator<Item = (N, V)>,
73        N: Into<String>,
74        V: Into<RefOr<Example>>,
75    >(
76        mut self,
77        examples: E,
78    ) -> Self {
79        self.examples.extend(
80            examples
81                .into_iter()
82                .map(|(name, example)| (name.into(), example.into())),
83        );
84
85        self
86    }
87
88    /// Add an encoding.
89    ///
90    /// The `property_name` MUST exist in the [`Content::schema`] as a property,
91    /// with `schema` being a [`Schema::Object`] and this object containing the same property
92    /// key in [`Object::properties`](crate::openapi::schema::Object::properties).
93    ///
94    /// The encoding object SHALL only apply to `request_body` objects when the media type is
95    /// multipart or `application/x-www-form-urlencoded`.
96    #[must_use]
97    pub fn encoding<S: Into<String>, E: Into<Encoding>>(
98        mut self,
99        property_name: S,
100        encoding: E,
101    ) -> Self {
102        self.encoding.insert(property_name.into(), encoding.into());
103        self
104    }
105}
106
107impl From<RefOr<Schema>> for Content {
108    fn from(schema: RefOr<Schema>) -> Self {
109        Self {
110            schema,
111            ..Self::default()
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use assert_json_diff::assert_json_eq;
119    use serde_json::{Map, json};
120
121    use super::*;
122
123    #[test]
124    fn test_build_content() {
125        let content = Content::new(RefOr::Ref(crate::Ref::from_schema_name("MySchema")))
126            .example(Value::Object(Map::from_iter([(
127                "schema".into(),
128                Value::String("MySchema".to_owned()),
129            )])))
130            .encoding(
131                "schema".to_owned(),
132                Encoding::default().content_type("text/plain"),
133            );
134        assert_json_eq!(
135            content,
136            json!({
137              "schema": {
138                "$ref": "#/components/schemas/MySchema"
139              },
140              "example": {
141                "schema": "MySchema"
142              },
143              "encoding": {
144                  "schema": {
145                    "contentType": "text/plain"
146                  }
147              }
148            })
149        );
150
151        let content = content
152            .schema(RefOr::Ref(crate::Ref::from_schema_name("NewSchema")))
153            .extend_examples([(
154                "example1".to_owned(),
155                Example::new().value(Value::Object(Map::from_iter([(
156                    "schema".into(),
157                    Value::String("MySchema".to_owned()),
158                )]))),
159            )]);
160        assert_json_eq!(
161            content,
162            json!({
163              "schema": {
164                "$ref": "#/components/schemas/NewSchema"
165              },
166              "example": {
167                "schema": "MySchema"
168              },
169              "examples": {
170                "example1": {
171                  "value": {
172                    "schema": "MySchema"
173                  }
174                }
175              },
176              "encoding": {
177                  "schema": {
178                    "contentType": "text/plain"
179                  }
180              }
181            })
182        );
183    }
184}