salvo_oapi/openapi/
content.rs

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