1use std::ops::{Deref, DerefMut};
5
6use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8
9use super::Content;
10use super::header::Header;
11use super::link::Link;
12use crate::{PropMap, Ref, RefOr};
13
14#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
20#[serde(rename_all = "camelCase")]
21pub struct Responses(PropMap<String, RefOr<Response>>);
22
23impl<K, R> From<PropMap<K, R>> for Responses
24where
25 K: Into<String>,
26 R: Into<RefOr<Response>>,
27{
28 fn from(inner: PropMap<K, R>) -> Self {
29 Self(
30 inner
31 .into_iter()
32 .map(|(k, v)| (k.into(), v.into()))
33 .collect(),
34 )
35 }
36}
37impl<K, R, const N: usize> From<[(K, R); N]> for Responses
38where
39 K: Into<String>,
40 R: Into<RefOr<Response>>,
41{
42 fn from(inner: [(K, R); N]) -> Self {
43 Self(
44 <[(K, R)]>::into_vec(Box::new(inner))
45 .into_iter()
46 .map(|(k, v)| (k.into(), v.into()))
47 .collect(),
48 )
49 }
50}
51
52impl Deref for Responses {
53 type Target = PropMap<String, RefOr<Response>>;
54
55 fn deref(&self) -> &Self::Target {
56 &self.0
57 }
58}
59
60impl DerefMut for Responses {
61 fn deref_mut(&mut self) -> &mut Self::Target {
62 &mut self.0
63 }
64}
65
66impl IntoIterator for Responses {
67 type Item = (String, RefOr<Response>);
68 type IntoIter = <PropMap<String, RefOr<Response>> as IntoIterator>::IntoIter;
69
70 fn into_iter(self) -> Self::IntoIter {
71 self.0.into_iter()
72 }
73}
74
75impl Responses {
76 #[must_use]
79 pub fn new() -> Self {
80 Default::default()
81 }
82 #[must_use]
84 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
85 mut self,
86 key: S,
87 response: R,
88 ) -> Self {
89 self.insert(key, response);
90 self
91 }
92
93 pub fn insert<S: Into<String>, R: Into<RefOr<Response>>>(&mut self, key: S, response: R) {
95 self.0.insert(key.into(), response.into());
96 }
97
98 pub fn append(&mut self, other: &mut Self) {
103 self.0.append(&mut other.0);
104 }
105
106 pub fn extend<I, C, R>(&mut self, iter: I)
108 where
109 I: IntoIterator<Item = (C, R)>,
110 C: Into<String>,
111 R: Into<RefOr<Response>>,
112 {
113 self.0.extend(
114 iter.into_iter()
115 .map(|(key, response)| (key.into(), response.into())),
116 );
117 }
118}
119
120impl From<Responses> for PropMap<String, RefOr<Response>> {
121 fn from(responses: Responses) -> Self {
122 responses.0
123 }
124}
125
126impl<C, R> FromIterator<(C, R)> for Responses
127where
128 C: Into<String>,
129 R: Into<RefOr<Response>>,
130{
131 fn from_iter<T: IntoIterator<Item = (C, R)>>(iter: T) -> Self {
132 Self(PropMap::from_iter(
133 iter.into_iter()
134 .map(|(key, response)| (key.into(), response.into())),
135 ))
136 }
137}
138
139#[non_exhaustive]
145#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
146#[serde(rename_all = "camelCase")]
147pub struct Response {
148 #[serde(skip_serializing_if = "Option::is_none")]
152 pub summary: Option<String>,
153
154 #[serde(default)]
160 pub description: String,
161
162 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
164 pub headers: PropMap<String, Header>,
165
166 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
172 #[serde(rename = "content")]
173 pub contents: IndexMap<String, Content>,
174
175 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
177 pub extensions: PropMap<String, serde_json::Value>,
178
179 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
182 pub links: PropMap<String, RefOr<Link>>,
183}
184
185impl Response {
186 #[must_use]
190 pub fn new<S: Into<String>>(description: S) -> Self {
191 Self {
192 description: description.into(),
193 ..Default::default()
194 }
195 }
196
197 #[must_use]
199 pub fn description<I: Into<String>>(mut self, description: I) -> Self {
200 self.description = description.into();
201 self
202 }
203
204 #[must_use]
206 pub fn summary<I: Into<String>>(mut self, summary: I) -> Self {
207 self.summary = Some(summary.into());
208 self
209 }
210
211 #[must_use]
214 pub fn add_content<S: Into<String>, C: Into<Content>>(mut self, key: S, content: C) -> Self {
215 self.contents.insert(key.into(), content.into());
216 self
217 }
218 #[must_use]
220 pub fn add_header<S: Into<String>>(mut self, name: S, header: Header) -> Self {
221 self.headers.insert(name.into(), header);
222 self
223 }
224
225 #[must_use]
227 pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
228 self.extensions.insert(key.into(), value);
229 self
230 }
231
232 #[must_use]
234 pub fn add_link<S: Into<String>, L: Into<RefOr<Link>>>(mut self, name: S, link: L) -> Self {
235 self.links.insert(name.into(), link.into());
236
237 self
238 }
239}
240
241impl From<Ref> for RefOr<Response> {
242 fn from(r: Ref) -> Self {
243 Self::Ref(r)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use assert_json_diff::assert_json_eq;
250 use serde_json::json;
251
252 use super::{Content, Header, PropMap, Ref, RefOr, Response, Responses};
253
254 #[test]
255 fn responses_new() {
256 let responses = Responses::new();
257 assert!(responses.is_empty());
258 }
259
260 #[test]
261 fn response_builder() -> Result<(), serde_json::Error> {
262 let request_body = Response::new("A sample response")
263 .description("A sample response description")
264 .add_content(
265 "application/json",
266 Content::new(Ref::from_schema_name("MySchemaPayload")),
267 )
268 .add_header(
269 "content-type",
270 Header::default().description("application/json"),
271 );
272
273 assert_json_eq!(
274 request_body,
275 json!({
276 "description": "A sample response description",
277 "content": {
278 "application/json": {
279 "schema": {
280 "$ref": "#/components/schemas/MySchemaPayload"
281 }
282 }
283 },
284 "headers": {
285 "content-type": {
286 "description": "application/json",
287 "schema": {
288 "type": "string"
289 }
290 }
291 }
292 })
293 );
294 Ok(())
295 }
296
297 #[test]
298 fn response_summary_serializes_and_description_is_optional_on_input() {
299 let response = Response::new("A sample response").summary("Sample");
300 assert_json_eq!(
301 response,
302 json!({ "summary": "Sample", "description": "A sample response" })
303 );
304
305 let parsed: Response =
307 serde_json::from_value(json!({ "summary": "Sample" })).expect("deserialize");
308 assert_eq!(parsed.summary.as_deref(), Some("Sample"));
309 assert_eq!(parsed.description, "");
310 }
311
312 #[test]
313 fn test_responses_from_btree_map() {
314 let input = PropMap::from([
315 ("response1".to_owned(), Response::new("response1")),
316 ("response2".to_owned(), Response::new("response2")),
317 ]);
318
319 let expected = Responses(PropMap::from([
320 (
321 "response1".to_owned(),
322 RefOr::Type(Response::new("response1")),
323 ),
324 (
325 "response2".to_owned(),
326 RefOr::Type(Response::new("response2")),
327 ),
328 ]));
329
330 let actual = Responses::from(input);
331
332 assert_eq!(expected, actual);
333 }
334
335 #[test]
336 fn test_responses_from_kv_sequence() {
337 let input = [
338 ("response1".to_owned(), Response::new("response1")),
339 ("response2".to_owned(), Response::new("response2")),
340 ];
341
342 let expected = Responses(PropMap::from([
343 (
344 "response1".to_owned(),
345 RefOr::Type(Response::new("response1")),
346 ),
347 (
348 "response2".to_owned(),
349 RefOr::Type(Response::new("response2")),
350 ),
351 ]));
352
353 let actual = Responses::from(input);
354
355 assert_eq!(expected, actual);
356 }
357
358 #[test]
359 fn test_responses_from_iter() {
360 let input = [
361 ("response1".to_owned(), Response::new("response1")),
362 ("response2".to_owned(), Response::new("response2")),
363 ];
364
365 let expected = Responses(PropMap::from([
366 (
367 "response1".to_owned(),
368 RefOr::Type(Response::new("response1")),
369 ),
370 (
371 "response2".to_owned(),
372 RefOr::Type(Response::new("response2")),
373 ),
374 ]));
375
376 let actual = Responses::from_iter(input);
377
378 assert_eq!(expected, actual);
379 }
380
381 #[test]
382 fn test_responses_into_iter() {
383 let responses = Responses::new();
384 let responses = responses.response("response1", Response::new("response1"));
385 assert_eq!(1, responses.into_iter().collect::<Vec<_>>().len());
386 }
387
388 #[test]
389 fn test_btree_map_from_responses() {
390 let expected = PropMap::from([
391 (
392 "response1".to_owned(),
393 RefOr::Type(Response::new("response1")),
394 ),
395 (
396 "response2".to_owned(),
397 RefOr::Type(Response::new("response2")),
398 ),
399 ]);
400
401 let actual = PropMap::from(
402 Responses::new()
403 .response("response1", Response::new("response1"))
404 .response("response2", Response::new("response2")),
405 );
406 assert_eq!(expected, actual);
407 }
408}