salvo_oapi/openapi/
content.rs1use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::encoding::Encoding;
6use super::example::Example;
7use super::{PropMap, RefOr, Schema};
8
9#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
11#[serde(rename_all = "camelCase")]
12#[non_exhaustive]
13pub struct Content {
14 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none", default)]
26 pub ref_location: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub schema: Option<RefOr<Schema>>,
31
32 #[serde(skip_serializing_if = "Option::is_none", default)]
40 pub item_schema: Option<RefOr<Schema>>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub example: Option<Value>,
45
46 #[serde(default, skip_serializing_if = "PropMap::is_empty")]
51 pub examples: PropMap<String, RefOr<Example>>,
52
53 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
64 pub encoding: PropMap<String, Encoding>,
65
66 #[serde(skip_serializing_if = "Vec::is_empty", default)]
72 pub prefix_encoding: Vec<Encoding>,
73
74 #[serde(skip_serializing_if = "Option::is_none", default)]
81 pub item_encoding: Option<Encoding>,
82
83 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
85 pub extensions: PropMap<String, serde_json::Value>,
86}
87
88impl Content {
89 #[must_use]
91 pub fn new<I: Into<RefOr<Schema>>>(schema: I) -> Self {
92 Self {
93 schema: Some(schema.into()),
94 ..Self::default()
95 }
96 }
97
98 #[must_use]
101 pub fn from_ref<S: Into<String>>(ref_location: S) -> Self {
102 Self {
103 ref_location: Some(ref_location.into()),
104 ..Self::default()
105 }
106 }
107
108 #[must_use]
110 pub fn ref_location<S: Into<String>>(mut self, ref_location: S) -> Self {
111 self.ref_location = Some(ref_location.into());
112 self
113 }
114
115 #[must_use]
117 pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
118 self.schema = Some(component.into());
119 self
120 }
121
122 #[must_use]
125 pub fn item_schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
126 self.item_schema = Some(component.into());
127 self
128 }
129
130 #[must_use]
132 pub fn example(mut self, example: Value) -> Self {
133 self.example = Some(example);
134 self
135 }
136
137 #[must_use]
145 pub fn extend_examples<
146 E: IntoIterator<Item = (N, V)>,
147 N: Into<String>,
148 V: Into<RefOr<Example>>,
149 >(
150 mut self,
151 examples: E,
152 ) -> Self {
153 self.examples.extend(
154 examples
155 .into_iter()
156 .map(|(name, example)| (name.into(), example.into())),
157 );
158
159 self
160 }
161
162 #[must_use]
164 pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
165 self.extensions = extensions;
166 self
167 }
168
169 #[must_use]
178 pub fn encoding<S: Into<String>, E: Into<Encoding>>(
179 mut self,
180 property_name: S,
181 encoding: E,
182 ) -> Self {
183 self.encoding.insert(property_name.into(), encoding.into());
184 self
185 }
186
187 #[must_use]
189 pub fn prefix_encoding<I: IntoIterator<Item = Encoding>>(mut self, prefix_encoding: I) -> Self {
190 self.prefix_encoding = prefix_encoding.into_iter().collect();
191 self
192 }
193
194 #[must_use]
197 pub fn item_encoding<E: Into<Encoding>>(mut self, item_encoding: E) -> Self {
198 self.item_encoding = Some(item_encoding.into());
199 self
200 }
201}
202
203impl From<RefOr<Schema>> for Content {
204 fn from(schema: RefOr<Schema>) -> Self {
205 Self {
206 schema: Some(schema),
207 ..Self::default()
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use assert_json_diff::assert_json_eq;
215 use serde_json::{Map, json};
216
217 use super::*;
218
219 #[test]
220 fn test_build_content() {
221 let content = Content::new(RefOr::Ref(crate::Ref::from_schema_name("MySchema")))
222 .example(Value::Object(Map::from_iter([(
223 "schema".into(),
224 Value::String("MySchema".to_owned()),
225 )])))
226 .encoding(
227 "schema".to_owned(),
228 Encoding::default().content_type("text/plain"),
229 );
230 assert_json_eq!(
231 content,
232 json!({
233 "schema": {
234 "$ref": "#/components/schemas/MySchema"
235 },
236 "example": {
237 "schema": "MySchema"
238 },
239 "encoding": {
240 "schema": {
241 "contentType": "text/plain"
242 }
243 }
244 })
245 );
246
247 let content = content
248 .schema(RefOr::Ref(crate::Ref::from_schema_name("NewSchema")))
249 .extend_examples([(
250 "example1".to_owned(),
251 Example::new().value(Value::Object(Map::from_iter([(
252 "schema".into(),
253 Value::String("MySchema".to_owned()),
254 )]))),
255 )]);
256 assert_json_eq!(
257 content,
258 json!({
259 "schema": {
260 "$ref": "#/components/schemas/NewSchema"
261 },
262 "example": {
263 "schema": "MySchema"
264 },
265 "examples": {
266 "example1": {
267 "value": {
268 "schema": "MySchema"
269 }
270 }
271 },
272 "encoding": {
273 "schema": {
274 "contentType": "text/plain"
275 }
276 }
277 })
278 );
279 }
280
281 #[test]
282 fn content_ref_serializes_as_a_reference_object() {
283 let content = Content::from_ref("#/components/mediaTypes/FramePayload");
284 let value = serde_json::to_value(&content).expect("serialize");
285 assert_json_eq!(
286 &value,
287 json!({ "$ref": "#/components/mediaTypes/FramePayload" })
288 );
289
290 let parsed: Content = serde_json::from_value(value).expect("deserialize");
291 assert_eq!(parsed, content);
292 assert!(parsed.schema.is_none());
293 }
294
295 #[test]
296 fn test_content_openapi_3_2_streaming_fields() {
297 let content = Content::default()
298 .item_schema(crate::Ref::from_schema_name("Frame"))
299 .prefix_encoding([Encoding::default().content_type("text/html")])
300 .item_encoding(Encoding::default().content_type("image/*"));
301
302 let value = serde_json::to_value(&content).expect("serialize");
303 assert_json_eq!(
304 &value,
305 json!({
306 "itemSchema": { "$ref": "#/components/schemas/Frame" },
307 "prefixEncoding": [ { "contentType": "text/html" } ],
308 "itemEncoding": { "contentType": "image/*" }
309 })
310 );
311
312 let parsed: Content = serde_json::from_value(value).expect("deserialize");
313 assert_eq!(parsed, content);
314 }
315}