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::{builder, header::Header, set_value, Content};
13
14builder! {
15 ResponsesBuilder;
16
17 #[non_exhaustive]
23 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
24 #[cfg_attr(feature = "debug", derive(Debug))]
25 #[serde(rename_all = "camelCase")]
26 pub struct Responses {
27 #[serde(flatten)]
29 pub responses: BTreeMap<String, RefOr<Response>>,
30 }
31}
32
33impl Responses {
34 pub fn new() -> Self {
35 Default::default()
36 }
37}
38
39impl ResponsesBuilder {
40 pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
42 mut self,
43 code: S,
44 response: R,
45 ) -> Self {
46 self.responses.insert(code.into(), response.into());
47
48 self
49 }
50
51 pub fn responses_from_iter<
53 I: IntoIterator<Item = (C, R)>,
54 C: Into<String>,
55 R: Into<RefOr<Response>>,
56 >(
57 mut self,
58 iter: I,
59 ) -> Self {
60 self.responses.extend(
61 iter.into_iter()
62 .map(|(code, response)| (code.into(), response.into())),
63 );
64 self
65 }
66
67 pub fn responses_from_into_responses<I: IntoResponses>(mut self) -> Self {
69 self.responses.extend(I::responses());
70 self
71 }
72}
73
74impl From<Responses> for BTreeMap<String, RefOr<Response>> {
75 fn from(responses: Responses) -> Self {
76 responses.responses
77 }
78}
79
80impl<C, R> FromIterator<(C, R)> for Responses
81where
82 C: Into<String>,
83 R: Into<RefOr<Response>>,
84{
85 fn from_iter<T: IntoIterator<Item = (C, R)>>(iter: T) -> Self {
86 Self {
87 responses: BTreeMap::from_iter(
88 iter.into_iter()
89 .map(|(code, response)| (code.into(), response.into())),
90 ),
91 }
92 }
93}
94
95builder! {
96 ResponseBuilder;
97
98 #[non_exhaustive]
104 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
105 #[cfg_attr(feature = "debug", derive(Debug))]
106 #[serde(rename_all = "camelCase")]
107 pub struct Response {
108 pub description: String,
110
111 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
113 pub headers: BTreeMap<String, Header>,
114
115 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
120 pub content: IndexMap<String, Content>,
121 }
122}
123
124impl Response {
125 pub fn new<S: Into<String>>(description: S) -> Self {
129 Self {
130 description: description.into(),
131 ..Default::default()
132 }
133 }
134}
135
136impl ResponseBuilder {
137 pub fn description<I: Into<String>>(mut self, description: I) -> Self {
139 set_value!(self description description.into())
140 }
141
142 pub fn content<S: Into<String>>(mut self, content_type: S, content: Content) -> Self {
144 self.content.insert(content_type.into(), content);
145
146 self
147 }
148
149 pub fn header<S: Into<String>>(mut self, name: S, header: Header) -> Self {
151 self.headers.insert(name.into(), header);
152
153 self
154 }
155}
156
157impl From<ResponseBuilder> for RefOr<Response> {
158 fn from(builder: ResponseBuilder) -> Self {
159 Self::T(builder.build())
160 }
161}
162
163impl From<Ref> for RefOr<Response> {
164 fn from(r: Ref) -> Self {
165 Self::Ref(r)
166 }
167}
168
169#[cfg(feature = "openapi_extensions")]
201#[cfg_attr(doc_cfg, doc(cfg(feature = "openapi_extensions")))]
202pub trait ResponseExt {
203 fn json_schema_ref(self, ref_name: &str) -> Self;
206}
207
208#[cfg(feature = "openapi_extensions")]
209impl ResponseExt for Response {
210 fn json_schema_ref(mut self, ref_name: &str) -> Response {
211 self.content.insert(
212 "application/json".to_string(),
213 Content::new(crate::openapi::Ref::from_schema_name(ref_name)),
214 );
215 self
216 }
217}
218
219#[cfg(feature = "openapi_extensions")]
220impl ResponseExt for ResponseBuilder {
221 fn json_schema_ref(self, ref_name: &str) -> ResponseBuilder {
222 self.content(
223 "application/json",
224 Content::new(crate::openapi::Ref::from_schema_name(ref_name)),
225 )
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::{Content, ResponseBuilder, Responses};
232 use assert_json_diff::assert_json_eq;
233 use serde_json::json;
234
235 #[test]
236 fn responses_new() {
237 let responses = Responses::new();
238
239 assert!(responses.responses.is_empty());
240 }
241
242 #[test]
243 fn response_builder() -> Result<(), serde_json::Error> {
244 let request_body = ResponseBuilder::new()
245 .description("A sample response")
246 .content(
247 "application/json",
248 Content::new(crate::openapi::Ref::from_schema_name("MySchemaPayload")),
249 )
250 .build();
251 let serialized = serde_json::to_string_pretty(&request_body)?;
252 println!("serialized json:\n {serialized}");
253 assert_json_eq!(
254 request_body,
255 json!({
256 "description": "A sample response",
257 "content": {
258 "application/json": {
259 "schema": {
260 "$ref": "#/components/schemas/MySchemaPayload"
261 }
262 }
263 }
264 })
265 );
266 Ok(())
267 }
268}
269
270#[cfg(all(test, feature = "openapi_extensions"))]
271mod openapi_extensions_tests {
272 use assert_json_diff::assert_json_eq;
273 use serde_json::json;
274
275 use crate::openapi::ResponseBuilder;
276
277 use super::ResponseExt;
278
279 #[test]
280 fn response_ext() {
281 let request_body = ResponseBuilder::new()
282 .description("A sample response")
283 .build()
284 .json_schema_ref("MySchemaPayload");
285
286 assert_json_eq!(
287 request_body,
288 json!({
289 "description": "A sample response",
290 "content": {
291 "application/json": {
292 "schema": {
293 "$ref": "#/components/schemas/MySchemaPayload"
294 }
295 }
296 }
297 })
298 );
299 }
300
301 #[test]
302 fn response_builder_ext() {
303 let request_body = ResponseBuilder::new()
304 .description("A sample response")
305 .json_schema_ref("MySchemaPayload")
306 .build();
307 assert_json_eq!(
308 request_body,
309 json!({
310 "description": "A sample response",
311 "content": {
312 "application/json": {
313 "schema": {
314 "$ref": "#/components/schemas/MySchemaPayload"
315 }
316 }
317 }
318 })
319 );
320 }
321}