1use std::collections::BTreeMap;
5
6use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8
9use crate::openapi::{Ref, RefOr};
10use crate::IntoResponses;
11
12use super::extensions::Extensions;
13use super::link::Link;
14use super::{builder, header::Header, set_value, Content};
15
16builder! {
17 ResponsesBuilder;
18
19 #[non_exhaustive]
25 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
26 #[cfg_attr(feature = "debug", derive(Debug))]
27 #[serde(rename_all = "camelCase")]
28 pub struct Responses {
29 #[serde(flatten)]
31 pub responses: BTreeMap<String, RefOr<Response>>,
32
33 #[serde(skip_serializing_if = "Option::is_none", flatten)]
35 pub extensions: Option<Extensions>,
36 }
37}
38
39impl Responses {
40 pub fn new() -> Self {
42 Default::default()
43 }
44}
45
46impl ResponsesBuilder {
47 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
49 mut self,
50 code: S,
51 response: R,
52 ) -> Self {
53 self.responses.insert(code.into(), response.into());
54
55 self
56 }
57
58 pub fn responses_from_iter<
60 I: IntoIterator<Item = (C, R)>,
61 C: Into<String>,
62 R: Into<RefOr<Response>>,
63 >(
64 mut self,
65 iter: I,
66 ) -> Self {
67 self.responses.extend(
68 iter.into_iter()
69 .map(|(code, response)| (code.into(), response.into())),
70 );
71 self
72 }
73
74 pub fn responses_from_into_responses<I: IntoResponses>(mut self) -> Self {
76 self.responses.extend(I::responses());
77 self
78 }
79
80 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
82 set_value!(self extensions extensions)
83 }
84}
85
86impl From<Responses> for BTreeMap<String, RefOr<Response>> {
87 fn from(responses: Responses) -> Self {
88 responses.responses
89 }
90}
91
92impl<C, R> FromIterator<(C, R)> for Responses
93where
94 C: Into<String>,
95 R: Into<RefOr<Response>>,
96{
97 fn from_iter<T: IntoIterator<Item = (C, R)>>(iter: T) -> Self {
98 Self {
99 responses: BTreeMap::from_iter(
100 iter.into_iter()
101 .map(|(code, response)| (code.into(), response.into())),
102 ),
103 ..Default::default()
104 }
105 }
106}
107
108builder! {
109 ResponseBuilder;
110
111 #[non_exhaustive]
117 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
118 #[cfg_attr(feature = "debug", derive(Debug))]
119 #[serde(rename_all = "camelCase")]
120 pub struct Response {
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub summary: Option<String>,
124
125 #[serde(skip_serializing_if = "String::is_empty", default)]
127 pub description: String,
128
129 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
131 pub headers: BTreeMap<String, RefOr<Header>>,
132
133 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
138 pub content: IndexMap<String, RefOr<Content>>,
139
140 #[serde(skip_serializing_if = "Option::is_none", flatten)]
142 pub extensions: Option<Extensions>,
143
144 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
147 pub links: BTreeMap<String, RefOr<Link>>,
148 }
149}
150
151impl Response {
152 pub fn new<S: Into<String>>(description: S) -> Self {
156 Self {
157 description: description.into(),
158 ..Default::default()
159 }
160 }
161}
162
163impl ResponseBuilder {
164 pub fn description<I: Into<String>>(mut self, description: I) -> Self {
166 set_value!(self description description.into())
167 }
168
169 pub fn summary<I: Into<String>>(mut self, summary: Option<I>) -> Self {
171 set_value!(self summary summary.map(Into::into))
172 }
173
174 pub fn content<S: Into<String>, C: Into<Content>>(
176 mut self,
177 content_type: S,
178 content: C,
179 ) -> Self {
180 self.content
181 .insert(content_type.into(), content.into().into());
182
183 self
184 }
185
186 pub fn content_ref<S: Into<String>>(mut self, content_type: S, content_ref: Ref) -> Self {
188 self.content
189 .insert(content_type.into(), RefOr::Ref(content_ref));
190
191 self
192 }
193
194 pub fn header<S: Into<String>>(mut self, name: S, header: Header) -> Self {
196 self.headers.insert(name.into(), header.into());
197
198 self
199 }
200
201 pub fn header_ref<S: Into<String>>(mut self, name: S, header: Ref) -> Self {
203 self.headers.insert(name.into(), RefOr::Ref(header));
204
205 self
206 }
207
208 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
210 set_value!(self extensions extensions)
211 }
212
213 pub fn link<S: Into<String>, L: Into<RefOr<Link>>>(mut self, name: S, link: L) -> Self {
215 self.links.insert(name.into(), link.into());
216
217 self
218 }
219}
220
221impl From<ResponseBuilder> for RefOr<Response> {
222 fn from(builder: ResponseBuilder) -> Self {
223 Self::T(builder.build())
224 }
225}
226
227impl From<Ref> for RefOr<Response> {
228 fn from(r: Ref) -> Self {
229 Self::Ref(r)
230 }
231}
232
233#[cfg(feature = "openapi_extensions")]
265#[cfg_attr(doc_cfg, doc(cfg(feature = "openapi_extensions")))]
266pub trait ResponseExt {
267 fn json_schema_ref(self, ref_name: &str) -> Self;
270}
271
272#[cfg(feature = "openapi_extensions")]
273impl ResponseExt for Response {
274 fn json_schema_ref(mut self, ref_name: &str) -> Response {
275 self.content.insert(
276 "application/json".to_string(),
277 Content::new(Some(crate::openapi::Ref::from_schema_name(ref_name))).into(),
278 );
279 self
280 }
281}
282
283#[cfg(feature = "openapi_extensions")]
284impl ResponseExt for ResponseBuilder {
285 fn json_schema_ref(self, ref_name: &str) -> ResponseBuilder {
286 self.content(
287 "application/json",
288 Content::new(Some(crate::openapi::Ref::from_schema_name(ref_name))),
289 )
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::{Content, ResponseBuilder, Responses};
296 use insta::assert_json_snapshot;
297
298 #[test]
299 fn responses_new() {
300 let responses = Responses::new();
301
302 assert!(responses.responses.is_empty());
303 }
304
305 #[test]
306 fn response_builder() {
307 let request_body = ResponseBuilder::new()
308 .description("A sample response")
309 .content(
310 "application/json",
311 Content::new(Some(crate::openapi::Ref::from_schema_name(
312 "MySchemaPayload",
313 ))),
314 )
315 .build();
316 assert_json_snapshot!(request_body);
317 }
318}
319
320#[cfg(all(test, feature = "openapi_extensions"))]
321mod openapi_extensions_tests {
322 use crate::openapi::ResponseBuilder;
323 use insta::assert_json_snapshot;
324
325 use super::ResponseExt;
326
327 #[test]
328 fn response_ext() {
329 let request_body = ResponseBuilder::new()
330 .description("A sample response")
331 .build()
332 .json_schema_ref("MySchemaPayload");
333
334 assert_json_snapshot!(request_body);
335 }
336
337 #[test]
338 fn response_builder_ext() {
339 let request_body = ResponseBuilder::new()
340 .description("A sample response")
341 .json_schema_ref("MySchemaPayload")
342 .build();
343 assert_json_snapshot!(request_body);
344 }
345}