utoipa/openapi/
response.rs1use 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 pub description: String,
123
124 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
126 pub headers: BTreeMap<String, Header>,
127
128 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
133 pub content: IndexMap<String, Content>,
134
135 #[serde(skip_serializing_if = "Option::is_none", flatten)]
137 pub extensions: Option<Extensions>,
138
139 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
142 pub links: BTreeMap<String, RefOr<Link>>,
143 }
144}
145
146impl Response {
147 pub fn new<S: Into<String>>(description: S) -> Self {
151 Self {
152 description: description.into(),
153 ..Default::default()
154 }
155 }
156}
157
158impl ResponseBuilder {
159 pub fn description<I: Into<String>>(mut self, description: I) -> Self {
161 set_value!(self description description.into())
162 }
163
164 pub fn content<S: Into<String>>(mut self, content_type: S, content: Content) -> Self {
166 self.content.insert(content_type.into(), content);
167
168 self
169 }
170
171 pub fn header<S: Into<String>>(mut self, name: S, header: Header) -> Self {
173 self.headers.insert(name.into(), header);
174
175 self
176 }
177
178 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
180 set_value!(self extensions extensions)
181 }
182
183 pub fn link<S: Into<String>, L: Into<RefOr<Link>>>(mut self, name: S, link: L) -> Self {
185 self.links.insert(name.into(), link.into());
186
187 self
188 }
189}
190
191impl From<ResponseBuilder> for RefOr<Response> {
192 fn from(builder: ResponseBuilder) -> Self {
193 Self::T(builder.build())
194 }
195}
196
197impl From<Ref> for RefOr<Response> {
198 fn from(r: Ref) -> Self {
199 Self::Ref(r)
200 }
201}
202
203#[cfg(feature = "openapi_extensions")]
235#[cfg_attr(doc_cfg, doc(cfg(feature = "openapi_extensions")))]
236pub trait ResponseExt {
237 fn json_schema_ref(self, ref_name: &str) -> Self;
240}
241
242#[cfg(feature = "openapi_extensions")]
243impl ResponseExt for Response {
244 fn json_schema_ref(mut self, ref_name: &str) -> Response {
245 self.content.insert(
246 "application/json".to_string(),
247 Content::new(Some(crate::openapi::Ref::from_schema_name(ref_name))),
248 );
249 self
250 }
251}
252
253#[cfg(feature = "openapi_extensions")]
254impl ResponseExt for ResponseBuilder {
255 fn json_schema_ref(self, ref_name: &str) -> ResponseBuilder {
256 self.content(
257 "application/json",
258 Content::new(Some(crate::openapi::Ref::from_schema_name(ref_name))),
259 )
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::{Content, ResponseBuilder, Responses};
266 use insta::assert_json_snapshot;
267
268 #[test]
269 fn responses_new() {
270 let responses = Responses::new();
271
272 assert!(responses.responses.is_empty());
273 }
274
275 #[test]
276 fn response_builder() {
277 let request_body = ResponseBuilder::new()
278 .description("A sample response")
279 .content(
280 "application/json",
281 Content::new(Some(crate::openapi::Ref::from_schema_name(
282 "MySchemaPayload",
283 ))),
284 )
285 .build();
286 assert_json_snapshot!(request_body);
287 }
288}
289
290#[cfg(all(test, feature = "openapi_extensions"))]
291mod openapi_extensions_tests {
292 use crate::openapi::ResponseBuilder;
293 use insta::assert_json_snapshot;
294
295 use super::ResponseExt;
296
297 #[test]
298 fn response_ext() {
299 let request_body = ResponseBuilder::new()
300 .description("A sample response")
301 .build()
302 .json_schema_ref("MySchemaPayload");
303
304 assert_json_snapshot!(request_body);
305 }
306
307 #[test]
308 fn response_builder_ext() {
309 let request_body = ResponseBuilder::new()
310 .description("A sample response")
311 .json_schema_ref("MySchemaPayload")
312 .build();
313 assert_json_snapshot!(request_body);
314 }
315}