1use crate::error::{ApiError, ErrorResponse};
4use bytes::Bytes;
5use http::{header, HeaderMap, HeaderValue, StatusCode};
6use http_body_util::Full;
7use serde::Serialize;
8use rustapi_openapi::{Operation, ResponseModifier, ResponseSpec, MediaType, SchemaRef, Schema};
9use std::collections::HashMap;
10
11pub type Response = http::Response<Full<Bytes>>;
13
14pub trait IntoResponse {
16 fn into_response(self) -> Response;
18}
19
20impl IntoResponse for Response {
22 fn into_response(self) -> Response {
23 self
24 }
25}
26
27impl IntoResponse for () {
29 fn into_response(self) -> Response {
30 http::Response::builder()
31 .status(StatusCode::OK)
32 .body(Full::new(Bytes::new()))
33 .unwrap()
34 }
35}
36
37impl IntoResponse for &'static str {
39 fn into_response(self) -> Response {
40 http::Response::builder()
41 .status(StatusCode::OK)
42 .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
43 .body(Full::new(Bytes::from(self)))
44 .unwrap()
45 }
46}
47
48impl IntoResponse for String {
50 fn into_response(self) -> Response {
51 http::Response::builder()
52 .status(StatusCode::OK)
53 .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
54 .body(Full::new(Bytes::from(self)))
55 .unwrap()
56 }
57}
58
59impl IntoResponse for StatusCode {
61 fn into_response(self) -> Response {
62 http::Response::builder()
63 .status(self)
64 .body(Full::new(Bytes::new()))
65 .unwrap()
66 }
67}
68
69impl<R: IntoResponse> IntoResponse for (StatusCode, R) {
71 fn into_response(self) -> Response {
72 let mut response = self.1.into_response();
73 *response.status_mut() = self.0;
74 response
75 }
76}
77
78impl<R: IntoResponse> IntoResponse for (StatusCode, HeaderMap, R) {
80 fn into_response(self) -> Response {
81 let mut response = self.2.into_response();
82 *response.status_mut() = self.0;
83 response.headers_mut().extend(self.1);
84 response
85 }
86}
87
88impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
90 fn into_response(self) -> Response {
91 match self {
92 Ok(v) => v.into_response(),
93 Err(e) => e.into_response(),
94 }
95 }
96}
97
98impl IntoResponse for ApiError {
101 fn into_response(self) -> Response {
102 let status = self.status;
103 let error_response = ErrorResponse::from(self);
104 let body = serde_json::to_vec(&error_response).unwrap_or_else(|_| {
105 br#"{"error":{"type":"internal_error","message":"Failed to serialize error"}}"#.to_vec()
106 });
107
108 http::Response::builder()
109 .status(status)
110 .header(header::CONTENT_TYPE, "application/json")
111 .body(Full::new(Bytes::from(body)))
112 .unwrap()
113 }
114}
115
116impl ResponseModifier for ApiError {
117 fn update_response(op: &mut Operation) {
118 op.responses.insert("400".to_string(), ResponseSpec {
121 description: "Bad Request".to_string(),
122 content: {
123 let mut map = HashMap::new();
124 map.insert("application/json".to_string(), MediaType {
125 schema: SchemaRef::Ref { reference: "#/components/schemas/ErrorSchema".to_string() },
126 });
127 Some(map)
128 },
129 ..Default::default()
130 });
131
132 op.responses.insert("500".to_string(), ResponseSpec {
134 description: "Internal Server Error".to_string(),
135 content: {
136 let mut map = HashMap::new();
137 map.insert("application/json".to_string(), MediaType {
138 schema: SchemaRef::Ref { reference: "#/components/schemas/ErrorSchema".to_string() },
139 });
140 Some(map)
141 },
142 ..Default::default()
143 });
144 }
145}
146
147#[derive(Debug, Clone)]
160pub struct Created<T>(pub T);
161
162impl<T: Serialize> IntoResponse for Created<T> {
163 fn into_response(self) -> Response {
164 match serde_json::to_vec(&self.0) {
165 Ok(body) => http::Response::builder()
166 .status(StatusCode::CREATED)
167 .header(header::CONTENT_TYPE, "application/json")
168 .body(Full::new(Bytes::from(body)))
169 .unwrap(),
170 Err(err) => ApiError::internal(format!("Failed to serialize response: {}", err))
171 .into_response(),
172 }
173 }
174}
175
176impl<T: for<'a> Schema<'a>> ResponseModifier for Created<T> {
177 fn update_response(op: &mut Operation) {
178 let (name, _) = T::schema();
179
180 let schema_ref = SchemaRef::Ref {
181 reference: format!("#/components/schemas/{}", name),
182 };
183
184 op.responses.insert("201".to_string(), ResponseSpec {
185 description: "Created".to_string(),
186 content: {
187 let mut map = HashMap::new();
188 map.insert("application/json".to_string(), MediaType {
189 schema: schema_ref,
190 });
191 Some(map)
192 },
193 ..Default::default()
194 });
195 }
196}
197
198#[derive(Debug, Clone, Copy)]
211pub struct NoContent;
212
213impl IntoResponse for NoContent {
214 fn into_response(self) -> Response {
215 http::Response::builder()
216 .status(StatusCode::NO_CONTENT)
217 .body(Full::new(Bytes::new()))
218 .unwrap()
219 }
220}
221
222impl ResponseModifier for NoContent {
223 fn update_response(op: &mut Operation) {
224 op.responses.insert("204".to_string(), ResponseSpec {
225 description: "No Content".to_string(),
226 ..Default::default()
227 });
228 }
229}
230
231#[derive(Debug, Clone)]
233pub struct Html<T>(pub T);
234
235impl<T: Into<String>> IntoResponse for Html<T> {
236 fn into_response(self) -> Response {
237 http::Response::builder()
238 .status(StatusCode::OK)
239 .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
240 .body(Full::new(Bytes::from(self.0.into())))
241 .unwrap()
242 }
243}
244
245impl<T> ResponseModifier for Html<T> {
246 fn update_response(op: &mut Operation) {
247 op.responses.insert("200".to_string(), ResponseSpec {
248 description: "HTML Content".to_string(),
249 content: {
250 let mut map = HashMap::new();
251 map.insert("text/html".to_string(), MediaType {
252 schema: SchemaRef::Inline(serde_json::json!({ "type": "string" })),
253 });
254 Some(map)
255 },
256 ..Default::default()
257 });
258 }
259}
260
261#[derive(Debug, Clone)]
263pub struct Redirect {
264 status: StatusCode,
265 location: HeaderValue,
266}
267
268impl Redirect {
269 pub fn to(uri: &str) -> Self {
271 Self {
272 status: StatusCode::FOUND,
273 location: HeaderValue::from_str(uri).expect("Invalid redirect URI"),
274 }
275 }
276
277 pub fn permanent(uri: &str) -> Self {
279 Self {
280 status: StatusCode::MOVED_PERMANENTLY,
281 location: HeaderValue::from_str(uri).expect("Invalid redirect URI"),
282 }
283 }
284
285 pub fn temporary(uri: &str) -> Self {
287 Self {
288 status: StatusCode::TEMPORARY_REDIRECT,
289 location: HeaderValue::from_str(uri).expect("Invalid redirect URI"),
290 }
291 }
292}
293
294impl IntoResponse for Redirect {
295 fn into_response(self) -> Response {
296 http::Response::builder()
297 .status(self.status)
298 .header(header::LOCATION, self.location)
299 .body(Full::new(Bytes::new()))
300 .unwrap()
301 }
302}
303
304impl ResponseModifier for Redirect {
305 fn update_response(op: &mut Operation) {
306 op.responses.insert("3xx".to_string(), ResponseSpec {
309 description: "Redirection".to_string(),
310 ..Default::default()
311 });
312 }
313}