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