openapiv3_1/
response.rs

1//! Implements [OpenApi Responses][responses].
2//!
3//! [responses]: https://spec.openapis.org/oas/latest.html#responses-object
4use indexmap::IndexMap;
5use serde_derive::{Deserialize, Serialize};
6
7use super::Content;
8use super::extensions::Extensions;
9use super::header::Header;
10use super::link::Link;
11use crate::{Ref, RefOr};
12
13/// Implements [OpenAPI Responses Object][responses].
14///
15/// Responses is a map holding api operation responses identified by their status code.
16///
17/// [responses]: https://spec.openapis.org/oas/latest.html#responses-object
18#[non_exhaustive]
19#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
20#[cfg_attr(feature = "debug", derive(Debug))]
21#[serde(rename_all = "camelCase")]
22#[builder(on(_, into))]
23pub struct Responses {
24    /// Map containing status code as a key with represented response as a value.
25    #[serde(flatten)]
26    #[builder(field)]
27    pub responses: IndexMap<String, RefOr<Response>>,
28
29    /// Optional extensions "x-something".
30    #[serde(skip_serializing_if = "Option::is_none", flatten)]
31    pub extensions: Option<Extensions>,
32}
33
34impl Responses {
35    /// Construct a new [`Responses`].
36    pub fn new() -> Self {
37        Default::default()
38    }
39}
40
41impl<S: responses_builder::State> ResponsesBuilder<S> {
42    /// Add a [`Response`].
43    pub fn response(mut self, code: impl Into<String>, response: impl Into<RefOr<Response>>) -> Self {
44        self.responses.insert(code.into(), response.into());
45
46        self
47    }
48
49    /// Add responses from an iterator over a pair of `(status_code, response): (String, Response)`.
50    pub fn responses_from_iter<I: IntoIterator<Item = (C, R)>, C: Into<String>, R: Into<RefOr<Response>>>(
51        mut self,
52        iter: I,
53    ) -> Self {
54        self.responses
55            .extend(iter.into_iter().map(|(code, response)| (code.into(), response.into())));
56        self
57    }
58}
59
60impl From<Responses> for IndexMap<String, RefOr<Response>> {
61    fn from(responses: Responses) -> Self {
62        responses.responses
63    }
64}
65
66impl<C, R> FromIterator<(C, R)> for Responses
67where
68    C: Into<String>,
69    R: Into<RefOr<Response>>,
70{
71    fn from_iter<T: IntoIterator<Item = (C, R)>>(iter: T) -> Self {
72        Self {
73            responses: IndexMap::from_iter(iter.into_iter().map(|(code, response)| (code.into(), response.into()))),
74            ..Default::default()
75        }
76    }
77}
78
79/// Implements [OpenAPI Response Object][response].
80///
81/// Response is api operation response.
82///
83/// [response]: https://spec.openapis.org/oas/latest.html#response-object
84#[non_exhaustive]
85#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder)]
86#[cfg_attr(feature = "debug", derive(Debug))]
87#[serde(rename_all = "camelCase")]
88#[builder(on(_, into))]
89pub struct Response {
90    /// Map of headers identified by their name. `Content-Type` header will be ignored.
91    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
92    #[builder(field)]
93    pub headers: IndexMap<String, Header>,
94
95    /// Map of response [`Content`] objects identified by response body content type e.g `application/json`.
96    ///
97    /// [`Content`]s are stored within [`IndexMap`] to retain their insertion order. Swagger UI
98    /// will create and show default example according to the first entry in `content` map.
99    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
100    #[builder(field)]
101    pub content: IndexMap<String, Content>,
102
103    /// A map of operations links that can be followed from the response. The key of the
104    /// map is a short name for the link.
105    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
106    #[builder(field)]
107    pub links: IndexMap<String, RefOr<Link>>,
108
109    /// Optional extensions "x-something".
110    #[serde(skip_serializing_if = "Option::is_none", flatten)]
111    pub extensions: Option<Extensions>,
112
113    /// Description of the response. Response support markdown syntax.
114    pub description: String,
115}
116
117impl Response {
118    /// Construct a new [`Response`].
119    ///
120    /// Function takes description as argument.
121    pub fn new<S: Into<String>>(description: S) -> Self {
122        Self {
123            description: description.into(),
124            ..Default::default()
125        }
126    }
127}
128
129impl<S: response_builder::State> ResponseBuilder<S> {
130    /// Add [`Content`] of the [`Response`] with content type e.g `application/json`.
131    pub fn content(mut self, content_type: impl Into<String>, content: impl Into<Content>) -> Self {
132        self.content.insert(content_type.into(), content.into());
133        self
134    }
135
136    /// Add response [`Header`].
137    pub fn header(mut self, name: impl Into<String>, header: impl Into<Header>) -> Self {
138        self.headers.insert(name.into(), header.into());
139        self
140    }
141
142    /// Add link that can be followed from the response.
143    pub fn link(mut self, name: impl Into<String>, link: impl Into<RefOr<Link>>) -> Self {
144        self.links.insert(name.into(), link.into());
145        self
146    }
147
148    /// Add [`Content`] of the [`Response`] with content type e.g `application/json`.
149    pub fn contents<A: Into<String>, B: Into<Content>>(self, contents: impl IntoIterator<Item = (A, B)>) -> Self {
150        contents.into_iter().fold(self, |this, (a, b)| this.content(a, b))
151    }
152
153    /// Add response [`Header`].
154    pub fn headers<A: Into<String>, B: Into<Header>>(self, headers: impl IntoIterator<Item = (A, B)>) -> Self {
155        headers.into_iter().fold(self, |this, (a, b)| this.header(a, b))
156    }
157
158    /// Add link that can be followed from the response.
159    pub fn links<A: Into<String>, B: Into<RefOr<Link>>>(self, links: impl IntoIterator<Item = (A, B)>) -> Self {
160        links.into_iter().fold(self, |this, (a, b)| this.link(a, b))
161    }
162}
163
164impl<S: response_builder::IsComplete> From<ResponseBuilder<S>> for Response {
165    fn from(builder: ResponseBuilder<S>) -> Self {
166        builder.build()
167    }
168}
169
170impl<S: response_builder::IsComplete> From<ResponseBuilder<S>> for RefOr<Response> {
171    fn from(builder: ResponseBuilder<S>) -> Self {
172        Self::T(builder.build())
173    }
174}
175
176impl From<Ref> for RefOr<Response> {
177    fn from(r: Ref) -> Self {
178        Self::Ref(r)
179    }
180}
181
182#[cfg(test)]
183#[cfg_attr(coverage_nightly, coverage(off))]
184mod tests {
185    use insta::assert_json_snapshot;
186
187    use super::{Content, Responses};
188    use crate::Response;
189
190    #[test]
191    fn responses_new() {
192        let responses = Responses::new();
193
194        assert!(responses.responses.is_empty());
195    }
196
197    #[test]
198    fn response_builder() {
199        let request_body = Response::builder()
200            .description("A sample response")
201            .content(
202                "application/json",
203                Content::new(Some(crate::Ref::from_schema_name("MySchemaPayload"))),
204            )
205            .build();
206        assert_json_snapshot!(request_body);
207    }
208}