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