Skip to main content

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    #[serde(skip_serializing_if = "Option::is_none")]
15    pub schema: Option<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    /// Optional extensions "x-something"
40    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
41    pub extensions: PropMap<String, serde_json::Value>,
42}
43
44impl Content {
45    /// Construct a new [`Content`].
46    #[must_use]
47    pub fn new<I: Into<RefOr<Schema>>>(schema: I) -> Self {
48        Self {
49            schema: Some(schema.into()),
50            ..Self::default()
51        }
52    }
53
54    /// Add schema.
55    #[must_use]
56    pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
57        self.schema = Some(component.into());
58        self
59    }
60
61    /// Add example of schema.
62    #[must_use]
63    pub fn example(mut self, example: Value) -> Self {
64        self.example = Some(example);
65        self
66    }
67
68    /// Add iterator of _`(N, V)`_ where `N` is name of example and `V` is [`Example`][example] to
69    /// [`Content`] of a request body or response body.
70    ///
71    /// [`Content::examples`] and [`Content::example`] are mutually exclusive. If both are defined
72    /// `examples` will override value in `example`.
73    ///
74    /// [example]: ../example/Example.html
75    #[must_use]
76    pub fn extend_examples<
77        E: IntoIterator<Item = (N, V)>,
78        N: Into<String>,
79        V: Into<RefOr<Example>>,
80    >(
81        mut self,
82        examples: E,
83    ) -> Self {
84        self.examples.extend(
85            examples
86                .into_iter()
87                .map(|(name, example)| (name.into(), example.into())),
88        );
89
90        self
91    }
92
93    /// Add openapi extensions (`x-something`) for [`Content`].
94    #[must_use]
95    pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
96        self.extensions = extensions;
97        self
98    }
99
100    /// Add an encoding.
101    ///
102    /// The `property_name` MUST exist in the [`Content::schema`] as a property,
103    /// with `schema` being a [`Schema::Object`] and this object containing the same property
104    /// key in [`Object::properties`](crate::openapi::schema::Object::properties).
105    ///
106    /// The encoding object SHALL only apply to `request_body` objects when the media type is
107    /// multipart or `application/x-www-form-urlencoded`.
108    #[must_use]
109    pub fn encoding<S: Into<String>, E: Into<Encoding>>(
110        mut self,
111        property_name: S,
112        encoding: E,
113    ) -> Self {
114        self.encoding.insert(property_name.into(), encoding.into());
115        self
116    }
117}
118
119impl From<RefOr<Schema>> for Content {
120    fn from(schema: RefOr<Schema>) -> Self {
121        Self {
122            schema: Some(schema),
123            ..Self::default()
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use assert_json_diff::assert_json_eq;
131    use serde_json::{Map, json};
132
133    use super::*;
134
135    #[test]
136    fn test_build_content() {
137        let content = Content::new(RefOr::Ref(crate::Ref::from_schema_name("MySchema")))
138            .example(Value::Object(Map::from_iter([(
139                "schema".into(),
140                Value::String("MySchema".to_owned()),
141            )])))
142            .encoding(
143                "schema".to_owned(),
144                Encoding::default().content_type("text/plain"),
145            );
146        assert_json_eq!(
147            content,
148            json!({
149              "schema": {
150                "$ref": "#/components/schemas/MySchema"
151              },
152              "example": {
153                "schema": "MySchema"
154              },
155              "encoding": {
156                  "schema": {
157                    "contentType": "text/plain"
158                  }
159              }
160            })
161        );
162
163        let content = content
164            .schema(RefOr::Ref(crate::Ref::from_schema_name("NewSchema")))
165            .extend_examples([(
166                "example1".to_owned(),
167                Example::new().value(Value::Object(Map::from_iter([(
168                    "schema".into(),
169                    Value::String("MySchema".to_owned()),
170                )]))),
171            )]);
172        assert_json_eq!(
173            content,
174            json!({
175              "schema": {
176                "$ref": "#/components/schemas/NewSchema"
177              },
178              "example": {
179                "schema": "MySchema"
180              },
181              "examples": {
182                "example1": {
183                  "value": {
184                    "schema": "MySchema"
185                  }
186                }
187              },
188              "encoding": {
189                  "schema": {
190                    "contentType": "text/plain"
191                  }
192              }
193            })
194        );
195    }
196}