1use std::collections::HashMap;
6use axum::{
7 http::{HeaderMap, HeaderName, HeaderValue, StatusCode},
8 response::{Json, Response, IntoResponse},
9 body::{Body, Bytes},
10};
11use serde::Serialize;
12use crate::error::{HttpError, HttpResult};
13
14pub use axum::http::StatusCode as ElifStatusCode;
16
17pub use axum::http::HeaderMap as ElifHeaderMap;
19
20#[derive(Debug)]
22pub struct ElifResponse {
23 status: StatusCode,
24 headers: HeaderMap,
25 body: ResponseBody,
26}
27
28#[derive(Debug)]
30pub enum ResponseBody {
31 Empty,
32 Text(String),
33 Bytes(Bytes),
34 Json(serde_json::Value),
35}
36
37impl ElifResponse {
38 pub fn new() -> Self {
40 Self {
41 status: StatusCode::OK,
42 headers: HeaderMap::new(),
43 body: ResponseBody::Empty,
44 }
45 }
46
47 pub fn with_status(status: StatusCode) -> Self {
49 Self {
50 status,
51 headers: HeaderMap::new(),
52 body: ResponseBody::Empty,
53 }
54 }
55
56 pub fn status(mut self, status: StatusCode) -> Self {
58 self.status = status;
59 self
60 }
61
62 pub fn header<K, V>(mut self, key: K, value: V) -> HttpResult<Self>
64 where
65 K: TryInto<HeaderName>,
66 K::Error: std::fmt::Display,
67 V: TryInto<HeaderValue>,
68 V::Error: std::fmt::Display,
69 {
70 let header_name = key.try_into()
71 .map_err(|e| HttpError::internal(format!("Invalid header name: {}", e)))?;
72 let header_value = value.try_into()
73 .map_err(|e| HttpError::internal(format!("Invalid header value: {}", e)))?;
74
75 self.headers.insert(header_name, header_value);
76 Ok(self)
77 }
78
79 pub fn content_type(self, content_type: &str) -> HttpResult<Self> {
81 self.header("content-type", content_type)
82 }
83
84 pub fn text<S: Into<String>>(mut self, text: S) -> Self {
86 self.body = ResponseBody::Text(text.into());
87 self
88 }
89
90 pub fn bytes(mut self, bytes: Bytes) -> Self {
92 self.body = ResponseBody::Bytes(bytes);
93 self
94 }
95
96 pub fn json<T: Serialize>(mut self, data: &T) -> HttpResult<Self> {
98 let json_value = serde_json::to_value(data)
99 .map_err(|e| HttpError::internal(format!("JSON serialization failed: {}", e)))?;
100 self.body = ResponseBody::Json(json_value);
101 Ok(self)
102 }
103
104 pub fn json_value(mut self, value: serde_json::Value) -> Self {
106 self.body = ResponseBody::Json(value);
107 self
108 }
109
110 pub fn build(mut self) -> HttpResult<Response<Body>> {
112 if !self.headers.contains_key("content-type") {
114 match &self.body {
115 ResponseBody::Json(_) => {
116 self = self.content_type("application/json")?;
117 }
118 ResponseBody::Text(_) => {
119 self = self.content_type("text/plain; charset=utf-8")?;
120 }
121 _ => {}
122 }
123 }
124
125 let body = match self.body {
126 ResponseBody::Empty => Body::empty(),
127 ResponseBody::Text(text) => Body::from(text),
128 ResponseBody::Bytes(bytes) => Body::from(bytes),
129 ResponseBody::Json(value) => {
130 let json_string = serde_json::to_string(&value)
131 .map_err(|e| HttpError::internal(format!("JSON serialization failed: {}", e)))?;
132 Body::from(json_string)
133 }
134 };
135
136 let mut response = Response::builder()
137 .status(self.status);
138
139 for (key, value) in self.headers.iter() {
141 response = response.header(key, value);
142 }
143
144 response.body(body)
145 .map_err(|e| HttpError::internal(format!("Failed to build response: {}", e)))
146 }
147}
148
149impl Default for ElifResponse {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155impl ElifResponse {
157 pub fn ok() -> Self {
159 Self::with_status(StatusCode::OK)
160 }
161
162 pub fn created() -> Self {
164 Self::with_status(StatusCode::CREATED)
165 }
166
167 pub fn no_content() -> Self {
169 Self::with_status(StatusCode::NO_CONTENT)
170 }
171
172 pub fn bad_request() -> Self {
174 Self::with_status(StatusCode::BAD_REQUEST)
175 }
176
177 pub fn unauthorized() -> Self {
179 Self::with_status(StatusCode::UNAUTHORIZED)
180 }
181
182 pub fn forbidden() -> Self {
184 Self::with_status(StatusCode::FORBIDDEN)
185 }
186
187 pub fn not_found() -> Self {
189 Self::with_status(StatusCode::NOT_FOUND)
190 }
191
192 pub fn unprocessable_entity() -> Self {
194 Self::with_status(StatusCode::UNPROCESSABLE_ENTITY)
195 }
196
197 pub fn internal_server_error() -> Self {
199 Self::with_status(StatusCode::INTERNAL_SERVER_ERROR)
200 }
201
202 pub fn json_ok<T: Serialize>(data: &T) -> HttpResult<Response<Body>> {
204 Self::ok().json(data)?.build()
205 }
206
207 pub fn json_error(status: StatusCode, message: &str) -> HttpResult<Response<Body>> {
209 let error_data = serde_json::json!({
210 "error": {
211 "code": status.as_u16(),
212 "message": message
213 }
214 });
215
216 Self::with_status(status)
217 .json_value(error_data)
218 .build()
219 }
220
221 pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response<Body>> {
223 let error_data = serde_json::json!({
224 "error": {
225 "code": 422,
226 "message": "Validation failed",
227 "details": errors
228 }
229 });
230
231 Self::unprocessable_entity()
232 .json_value(error_data)
233 .build()
234 }
235}
236
237pub trait IntoElifResponse {
239 fn into_elif_response(self) -> HttpResult<ElifResponse>;
240}
241
242impl IntoElifResponse for String {
243 fn into_elif_response(self) -> HttpResult<ElifResponse> {
244 Ok(ElifResponse::ok().text(self))
245 }
246}
247
248impl IntoElifResponse for &str {
249 fn into_elif_response(self) -> HttpResult<ElifResponse> {
250 Ok(ElifResponse::ok().text(self))
251 }
252}
253
254impl IntoElifResponse for StatusCode {
255 fn into_elif_response(self) -> HttpResult<ElifResponse> {
256 Ok(ElifResponse::with_status(self))
257 }
258}
259
260impl IntoResponse for ElifResponse {
262 fn into_response(self) -> Response {
263 match self.build() {
264 Ok(response) => response,
265 Err(e) => {
266 (StatusCode::INTERNAL_SERVER_ERROR, format!("Response build failed: {}", e)).into_response()
268 }
269 }
270 }
271}
272
273impl ElifResponse {
275 pub fn redirect_permanent(location: &str) -> HttpResult<Self> {
277 Ok(Self::with_status(StatusCode::MOVED_PERMANENTLY)
278 .header("location", location)?)
279 }
280
281 pub fn redirect_temporary(location: &str) -> HttpResult<Self> {
283 Ok(Self::with_status(StatusCode::FOUND)
284 .header("location", location)?)
285 }
286
287 pub fn redirect_see_other(location: &str) -> HttpResult<Self> {
289 Ok(Self::with_status(StatusCode::SEE_OTHER)
290 .header("location", location)?)
291 }
292}
293
294impl ElifResponse {
296 pub fn download(filename: &str, content: Bytes) -> HttpResult<Self> {
298 let content_disposition = format!("attachment; filename=\"{}\"", filename);
299
300 Ok(Self::ok()
301 .header("content-disposition", content_disposition)?
302 .header("content-type", "application/octet-stream")?
303 .bytes(content))
304 }
305
306 pub fn file_inline(filename: &str, content_type: &str, content: Bytes) -> HttpResult<Self> {
308 let content_disposition = format!("inline; filename=\"{}\"", filename);
309
310 Ok(Self::ok()
311 .header("content-disposition", content_disposition)?
312 .header("content-type", content_type)?
313 .bytes(content))
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use serde_json::json;
321
322 #[test]
323 fn test_basic_response_building() {
324 let response = ElifResponse::ok()
325 .text("Hello, World!");
326
327 assert_eq!(response.status, StatusCode::OK);
328 match response.body {
329 ResponseBody::Text(text) => assert_eq!(text, "Hello, World!"),
330 _ => panic!("Expected text body"),
331 }
332 }
333
334 #[test]
335 fn test_json_response() {
336 let data = json!({
337 "name": "John Doe",
338 "age": 30
339 });
340
341 let response = ElifResponse::ok()
342 .json_value(data.clone());
343
344 match response.body {
345 ResponseBody::Json(value) => assert_eq!(value, data),
346 _ => panic!("Expected JSON body"),
347 }
348 }
349
350 #[test]
351 fn test_status_codes() {
352 assert_eq!(ElifResponse::created().status, StatusCode::CREATED);
353 assert_eq!(ElifResponse::not_found().status, StatusCode::NOT_FOUND);
354 assert_eq!(ElifResponse::internal_server_error().status, StatusCode::INTERNAL_SERVER_ERROR);
355 }
356
357 #[test]
358 fn test_headers() {
359 let response = ElifResponse::ok()
360 .header("x-custom-header", "test-value")
361 .unwrap();
362
363 assert!(response.headers.contains_key("x-custom-header"));
364 assert_eq!(
365 response.headers.get("x-custom-header").unwrap(),
366 &HeaderValue::from_static("test-value")
367 );
368 }
369
370 #[test]
371 fn test_redirect_responses() {
372 let redirect = ElifResponse::redirect_permanent("/new-location").unwrap();
373 assert_eq!(redirect.status, StatusCode::MOVED_PERMANENTLY);
374 assert!(redirect.headers.contains_key("location"));
375 }
376}