1use 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#[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 #[serde(flatten)]
26 #[builder(field)]
27 pub responses: IndexMap<String, RefOr<Response>>,
28
29 #[serde(skip_serializing_if = "Option::is_none", flatten)]
31 pub extensions: Option<Extensions>,
32}
33
34impl Responses {
35 pub fn new() -> Self {
37 Default::default()
38 }
39}
40
41impl<S: responses_builder::State> ResponsesBuilder<S> {
42 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 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#[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 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
92 #[builder(field)]
93 pub headers: IndexMap<String, Header>,
94
95 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
100 #[builder(field)]
101 pub content: IndexMap<String, Content>,
102
103 #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
106 #[builder(field)]
107 pub links: IndexMap<String, RefOr<Link>>,
108
109 #[serde(skip_serializing_if = "Option::is_none", flatten)]
111 pub extensions: Option<Extensions>,
112
113 pub description: String,
115}
116
117impl Response {
118 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 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 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 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 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 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 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}