1use super::{ElifHeaderMap, ElifHeaderName, ElifHeaderValue, ElifStatusCode};
6use crate::errors::{HttpError, HttpResult};
7use axum::{
8 body::{Body, Bytes},
9 response::{IntoResponse, Response},
10};
11use serde::Serialize;
12
13#[derive(Debug)]
15pub struct ElifResponse {
16 status: ElifStatusCode,
17 headers: ElifHeaderMap,
18 body: ResponseBody,
19}
20
21#[derive(Debug)]
23pub enum ResponseBody {
24 Empty,
25 Text(String),
26 Bytes(Bytes),
27 Json(serde_json::Value),
28}
29
30impl ElifResponse {
31 pub fn new() -> Self {
33 Self {
34 status: ElifStatusCode::OK,
35 headers: ElifHeaderMap::new(),
36 body: ResponseBody::Empty,
37 }
38 }
39
40 pub fn with_status(status: ElifStatusCode) -> Self {
42 Self {
43 status,
44 headers: ElifHeaderMap::new(),
45 body: ResponseBody::Empty,
46 }
47 }
48
49 pub fn status(mut self, status: ElifStatusCode) -> Self {
51 self.status = status;
52 self
53 }
54
55 pub fn set_status(&mut self, status: ElifStatusCode) {
57 self.status = status;
58 }
59
60 pub fn status_code(&self) -> ElifStatusCode {
62 self.status
63 }
64
65 pub fn headers(&self) -> &ElifHeaderMap {
67 &self.headers
68 }
69
70 pub fn headers_mut(&mut self) -> &mut ElifHeaderMap {
72 &mut self.headers
73 }
74
75 pub fn has_header<K: AsRef<str>>(&self, key: K) -> bool {
77 self.headers.contains_key_str(key.as_ref())
78 }
79
80 pub fn get_header<K: AsRef<str>>(&self, key: K) -> Option<&ElifHeaderValue> {
82 self.headers.get_str(key.as_ref())
83 }
84
85 pub fn with_header<K, V>(self, key: K, value: V) -> Self
92 where
93 K: AsRef<str>,
94 V: AsRef<str>,
95 {
96 self.header(key, value).unwrap_or_else(|err| {
97 tracing::error!("Header creation failed in with_header: {}", err);
98 ElifResponse::internal_server_error()
99 })
100 }
101
102 pub fn with_json<T: Serialize>(self, data: &T) -> Self {
107 self.json(data).unwrap_or_else(|err| {
108 tracing::error!("JSON serialization failed in with_json: {}", err);
109 ElifResponse::internal_server_error()
110 })
111 }
112
113 pub fn with_text<S: AsRef<str>>(mut self, content: S) -> Self {
117 self.body = ResponseBody::Text(content.as_ref().to_string());
118 self
119 }
120
121 pub fn with_html<S: AsRef<str>>(self, content: S) -> Self {
125 self.with_text(content)
126 .with_header("content-type", "text/html; charset=utf-8")
127 }
128
129 pub fn header<K, V>(mut self, key: K, value: V) -> HttpResult<Self>
131 where
132 K: AsRef<str>,
133 V: AsRef<str>,
134 {
135 let header_name = ElifHeaderName::from_str(key.as_ref())
136 .map_err(|e| HttpError::internal(format!("Invalid header name: {}", e)))?;
137 let header_value = ElifHeaderValue::from_str(value.as_ref())
138 .map_err(|e| HttpError::internal(format!("Invalid header value: {}", e)))?;
139
140 self.headers.insert(header_name, header_value);
141 Ok(self)
142 }
143
144 pub fn add_header<K, V>(&mut self, key: K, value: V) -> HttpResult<()>
146 where
147 K: AsRef<str>,
148 V: AsRef<str>,
149 {
150 let header_name = ElifHeaderName::from_str(key.as_ref())
151 .map_err(|e| HttpError::internal(format!("Invalid header name: {}", e)))?;
152 let header_value = ElifHeaderValue::from_str(value.as_ref())
153 .map_err(|e| HttpError::internal(format!("Invalid header value: {}", e)))?;
154
155 self.headers.insert(header_name, header_value);
156 Ok(())
157 }
158
159 pub fn remove_header<K: AsRef<str>>(&mut self, key: K) -> Option<ElifHeaderValue> {
161 self.headers.remove_header(key.as_ref())
162 }
163
164 pub fn content_type(self, content_type: &str) -> HttpResult<Self> {
166 self.header("content-type", content_type)
167 }
168
169 pub fn set_content_type(&mut self, content_type: &str) -> HttpResult<()> {
171 self.add_header("content-type", content_type)
172 }
173
174 pub fn text<S: Into<String>>(mut self, text: S) -> Self {
176 self.body = ResponseBody::Text(text.into());
177 self
178 }
179
180 pub fn set_text<S: Into<String>>(&mut self, text: S) {
182 self.body = ResponseBody::Text(text.into());
183 }
184
185 pub fn bytes(mut self, bytes: Bytes) -> Self {
187 self.body = ResponseBody::Bytes(bytes);
188 self
189 }
190
191 pub fn set_bytes(&mut self, bytes: Bytes) {
193 self.body = ResponseBody::Bytes(bytes);
194 }
195
196 pub fn json<T: Serialize>(mut self, data: &T) -> HttpResult<Self> {
198 let json_value = serde_json::to_value(data)
199 .map_err(|e| HttpError::internal(format!("JSON serialization failed: {}", e)))?;
200 self.body = ResponseBody::Json(json_value);
201 Ok(self)
202 }
203
204 pub fn set_json<T: Serialize>(&mut self, data: &T) -> HttpResult<()> {
206 let json_value = serde_json::to_value(data)
207 .map_err(|e| HttpError::internal(format!("JSON serialization failed: {}", e)))?;
208 self.body = ResponseBody::Json(json_value);
209 Ok(())
210 }
211
212 pub fn json_value(mut self, value: serde_json::Value) -> Self {
214 self.body = ResponseBody::Json(value);
215 self
216 }
217
218 pub fn set_json_value(&mut self, value: serde_json::Value) {
220 self.body = ResponseBody::Json(value);
221 }
222
223 pub fn build(mut self) -> HttpResult<Response<Body>> {
225 if !self.headers.contains_key_str("content-type") {
227 match &self.body {
228 ResponseBody::Json(_) => {
229 self = self.content_type("application/json")?;
230 }
231 ResponseBody::Text(_) => {
232 self = self.content_type("text/plain; charset=utf-8")?;
233 }
234 _ => {}
235 }
236 }
237
238 let body = match self.body {
239 ResponseBody::Empty => Body::empty(),
240 ResponseBody::Text(text) => Body::from(text),
241 ResponseBody::Bytes(bytes) => Body::from(bytes),
242 ResponseBody::Json(value) => {
243 let json_string = serde_json::to_string(&value).map_err(|e| {
244 HttpError::internal(format!("JSON serialization failed: {}", e))
245 })?;
246 Body::from(json_string)
247 }
248 };
249
250 let mut response = Response::builder().status(self.status.to_axum());
251
252 for (key, value) in self.headers.iter() {
254 response = response.header(key.to_axum(), value.to_axum());
255 }
256
257 response
258 .body(body)
259 .map_err(|e| HttpError::internal(format!("Failed to build response: {}", e)))
260 }
261
262 pub(crate) fn into_axum_response(self) -> Response<Body> {
264 IntoResponse::into_response(self)
265 }
266
267 pub(crate) async fn from_axum_response(response: Response<Body>) -> Self {
269 let (parts, body) = response.into_parts();
270
271 let body_bytes = match axum::body::to_bytes(body, usize::MAX).await {
273 Ok(bytes) => bytes,
274 Err(_) => Bytes::new(),
275 };
276
277 let mut elif_response = Self::with_status(ElifStatusCode::from_axum(parts.status));
278 let headers = parts.headers.clone();
279 elif_response.headers = ElifHeaderMap::from_axum(parts.headers);
280
281 if let Some(content_type) = headers.get("content-type") {
283 if let Ok(content_type_str) = content_type.to_str() {
284 if content_type_str.contains("application/json") {
285 if let Ok(json_value) = serde_json::from_slice::<serde_json::Value>(&body_bytes)
287 {
288 elif_response.body = ResponseBody::Json(json_value);
289 } else {
290 elif_response.body = ResponseBody::Bytes(body_bytes);
291 }
292 } else if content_type_str.starts_with("text/") {
293 match String::from_utf8(body_bytes.to_vec()) {
295 Ok(text) => elif_response.body = ResponseBody::Text(text),
296 Err(_) => elif_response.body = ResponseBody::Bytes(body_bytes),
297 }
298 } else {
299 elif_response.body = ResponseBody::Bytes(body_bytes);
300 }
301 } else {
302 elif_response.body = ResponseBody::Bytes(body_bytes);
303 }
304 } else if body_bytes.is_empty() {
305 elif_response.body = ResponseBody::Empty;
306 } else {
307 elif_response.body = ResponseBody::Bytes(body_bytes);
308 }
309
310 elif_response
311 }
312}
313
314impl Default for ElifResponse {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320impl ElifResponse {
322 pub fn ok() -> Self {
324 Self::with_status(ElifStatusCode::OK)
325 }
326
327 pub fn created() -> Self {
329 Self::with_status(ElifStatusCode::CREATED)
330 }
331
332 pub fn no_content() -> Self {
334 Self::with_status(ElifStatusCode::NO_CONTENT)
335 }
336
337 pub fn bad_request() -> Self {
339 Self::with_status(ElifStatusCode::BAD_REQUEST)
340 }
341
342 pub fn unauthorized() -> Self {
344 Self::with_status(ElifStatusCode::UNAUTHORIZED)
345 }
346
347 pub fn forbidden() -> Self {
349 Self::with_status(ElifStatusCode::FORBIDDEN)
350 }
351
352 pub fn not_found() -> Self {
354 Self::with_status(ElifStatusCode::NOT_FOUND)
355 }
356
357 pub fn unprocessable_entity() -> Self {
359 Self::with_status(ElifStatusCode::UNPROCESSABLE_ENTITY)
360 }
361
362 pub fn internal_server_error() -> Self {
364 Self::with_status(ElifStatusCode::INTERNAL_SERVER_ERROR)
365 }
366
367 pub fn json_ok<T: Serialize>(data: &T) -> HttpResult<Response<Body>> {
369 Self::ok().json(data)?.build()
370 }
371
372 pub fn json_error(status: ElifStatusCode, message: &str) -> HttpResult<Response<Body>> {
374 let error_data = serde_json::json!({
375 "error": {
376 "code": status.as_u16(),
377 "message": message
378 }
379 });
380
381 Self::with_status(status).json_value(error_data).build()
382 }
383
384 pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response<Body>> {
386 let error_data = serde_json::json!({
387 "error": {
388 "code": 422,
389 "message": "Validation failed",
390 "details": errors
391 }
392 });
393
394 Self::unprocessable_entity().json_value(error_data).build()
395 }
396}
397
398pub trait IntoElifResponse {
400 fn into_response(self) -> ElifResponse;
401}
402
403impl IntoElifResponse for String {
404 fn into_response(self) -> ElifResponse {
405 ElifResponse::ok().text(self)
406 }
407}
408
409impl IntoElifResponse for &str {
410 fn into_response(self) -> ElifResponse {
411 ElifResponse::ok().text(self)
412 }
413}
414
415impl IntoElifResponse for ElifStatusCode {
416 fn into_response(self) -> ElifResponse {
417 ElifResponse::with_status(self)
418 }
419}
420
421impl IntoElifResponse for ElifResponse {
422 fn into_response(self) -> ElifResponse {
423 self
424 }
425}
426
427impl IntoResponse for ElifResponse {
429 fn into_response(self) -> Response {
430 match self.build() {
431 Ok(response) => response,
432 Err(e) => {
433 (
435 ElifStatusCode::INTERNAL_SERVER_ERROR.to_axum(),
436 format!("Response build failed: {}", e),
437 )
438 .into_response()
439 }
440 }
441 }
442}
443
444impl ElifResponse {
446 pub fn redirect_permanent(location: &str) -> HttpResult<Self> {
448 Self::with_status(ElifStatusCode::MOVED_PERMANENTLY).header("location", location)
449 }
450
451 pub fn redirect_temporary(location: &str) -> HttpResult<Self> {
453 Self::with_status(ElifStatusCode::FOUND).header("location", location)
454 }
455
456 pub fn redirect_see_other(location: &str) -> HttpResult<Self> {
458 Self::with_status(ElifStatusCode::SEE_OTHER).header("location", location)
459 }
460}
461
462impl ElifResponse {
464 pub fn download(filename: &str, content: Bytes) -> HttpResult<Self> {
466 let content_disposition = format!("attachment; filename=\"{}\"", filename);
467
468 Ok(Self::ok()
469 .header("content-disposition", content_disposition)?
470 .header("content-type", "application/octet-stream")?
471 .bytes(content))
472 }
473
474 pub fn file_inline(filename: &str, content_type: &str, content: Bytes) -> HttpResult<Self> {
476 let content_disposition = format!("inline; filename=\"{}\"", filename);
477
478 Ok(Self::ok()
479 .header("content-disposition", content_disposition)?
480 .header("content-type", content_type)?
481 .bytes(content))
482 }
483
484 pub fn file<P: AsRef<std::path::Path>>(path: P) -> HttpResult<Self> {
486 let path = path.as_ref();
487 let content = std::fs::read(path)
488 .map_err(|e| HttpError::internal(format!("Failed to read file: {}", e)))?;
489
490 let mime_type = Self::guess_mime_type(path);
491
492 Ok(Self::ok()
493 .header("content-type", mime_type)?
494 .bytes(Bytes::from(content)))
495 }
496
497 fn guess_mime_type(path: &std::path::Path) -> &'static str {
499 match path.extension().and_then(|ext| ext.to_str()) {
500 Some("html") | Some("htm") => "text/html; charset=utf-8",
501 Some("css") => "text/css",
502 Some("js") => "application/javascript",
503 Some("json") => "application/json",
504 Some("xml") => "application/xml",
505 Some("pdf") => "application/pdf",
506 Some("txt") => "text/plain; charset=utf-8",
507 Some("png") => "image/png",
508 Some("jpg") | Some("jpeg") => "image/jpeg",
509 Some("gif") => "image/gif",
510 Some("svg") => "image/svg+xml",
511 Some("ico") => "image/x-icon",
512 Some("woff") => "font/woff",
513 Some("woff2") => "font/woff2",
514 Some("ttf") => "font/ttf",
515 Some("mp4") => "video/mp4",
516 Some("webm") => "video/webm",
517 Some("mp3") => "audio/mpeg",
518 Some("wav") => "audio/wav",
519 Some("zip") => "application/zip",
520 Some("tar") => "application/x-tar",
521 Some("gz") => "application/gzip",
522 _ => "application/octet-stream",
523 }
524 }
525}
526
527impl ElifResponse {
529 pub fn json_with_status<T: Serialize>(status: ElifStatusCode, data: &T) -> HttpResult<Self> {
531 Self::with_status(status).json(data)
532 }
533
534 pub fn json_raw(value: serde_json::Value) -> Self {
536 Self::ok().json_value(value)
537 }
538
539 pub fn json_raw_with_status(status: ElifStatusCode, value: serde_json::Value) -> Self {
541 Self::with_status(status).json_value(value)
542 }
543
544 pub fn text_with_type(content: &str, content_type: &str) -> HttpResult<Self> {
546 Self::ok()
547 .text(content)
548 .header("content-type", content_type)
549 }
550
551 pub fn xml<S: AsRef<str>>(content: S) -> HttpResult<Self> {
553 Self::text_with_type(content.as_ref(), "application/xml; charset=utf-8")
554 }
555
556 pub fn csv<S: AsRef<str>>(content: S) -> HttpResult<Self> {
558 Self::text_with_type(content.as_ref(), "text/csv; charset=utf-8")
559 }
560
561 pub fn javascript<S: AsRef<str>>(content: S) -> HttpResult<Self> {
563 Self::text_with_type(content.as_ref(), "application/javascript; charset=utf-8")
564 }
565
566 pub fn css<S: AsRef<str>>(content: S) -> HttpResult<Self> {
568 Self::text_with_type(content.as_ref(), "text/css; charset=utf-8")
569 }
570
571 pub fn stream() -> HttpResult<Self> {
573 Self::ok().header("transfer-encoding", "chunked")
574 }
575
576 pub fn sse() -> HttpResult<Self> {
578 Self::ok()
579 .header("content-type", "text/event-stream")?
580 .header("cache-control", "no-cache")?
581 .header("connection", "keep-alive")
582 }
583
584 pub fn jsonp<T: Serialize>(callback: &str, data: &T) -> HttpResult<Self> {
586 let json_data = serde_json::to_string(data)
587 .map_err(|e| HttpError::internal(format!("JSON serialization failed: {}", e)))?;
588
589 let jsonp_content = format!("{}({});", callback, json_data);
590
591 Self::ok()
592 .text(jsonp_content)
593 .header("content-type", "application/javascript; charset=utf-8")
594 }
595
596 pub fn image(content: Bytes, format: ImageFormat) -> HttpResult<Self> {
598 let content_type = match format {
599 ImageFormat::Png => "image/png",
600 ImageFormat::Jpeg => "image/jpeg",
601 ImageFormat::Gif => "image/gif",
602 ImageFormat::WebP => "image/webp",
603 ImageFormat::Svg => "image/svg+xml",
604 };
605
606 Ok(Self::ok()
607 .header("content-type", content_type)?
608 .bytes(content))
609 }
610
611 pub fn binary(content: Bytes, mime_type: &str) -> HttpResult<Self> {
613 Ok(Self::ok().header("content-type", mime_type)?.bytes(content))
614 }
615
616 pub fn cors_preflight(
618 allowed_origins: &[&str],
619 allowed_methods: &[&str],
620 allowed_headers: &[&str],
621 max_age: Option<u32>,
622 ) -> HttpResult<Self> {
623 let mut response = Self::no_content()
624 .header("access-control-allow-origin", allowed_origins.join(","))?
625 .header("access-control-allow-methods", allowed_methods.join(","))?
626 .header("access-control-allow-headers", allowed_headers.join(","))?;
627
628 if let Some(max_age) = max_age {
629 response = response.header("access-control-max-age", max_age.to_string())?;
630 }
631
632 Ok(response)
633 }
634
635 pub fn with_cors(
637 mut self,
638 origin: &str,
639 credentials: bool,
640 exposed_headers: Option<&[&str]>,
641 ) -> HttpResult<Self> {
642 self = self.header("access-control-allow-origin", origin)?;
643
644 if credentials {
645 self = self.header("access-control-allow-credentials", "true")?;
646 }
647
648 if let Some(headers) = exposed_headers {
649 self = self.header("access-control-expose-headers", headers.join(","))?;
650 }
651
652 Ok(self)
653 }
654
655 pub fn with_cache(mut self, max_age: u32, public: bool) -> HttpResult<Self> {
657 let cache_control = if public {
658 format!("public, max-age={}", max_age)
659 } else {
660 format!("private, max-age={}", max_age)
661 };
662
663 self = self.header("cache-control", cache_control)?;
664
665 let etag = format!("\"{}\"", self.generate_etag());
667 self = self.header("etag", etag)?;
668
669 Ok(self)
670 }
671
672 fn generate_etag(&self) -> String {
674 use std::collections::hash_map::DefaultHasher;
675 use std::hash::{Hash, Hasher};
676
677 let mut hasher = DefaultHasher::new();
678 self.status.as_u16().hash(&mut hasher);
679
680 for (key, value) in self.headers.iter() {
682 key.as_str().hash(&mut hasher);
683 if let Ok(value_str) = value.to_str() {
684 value_str.hash(&mut hasher);
685 }
686 }
687
688 match &self.body {
690 ResponseBody::Empty => "empty".hash(&mut hasher),
691 ResponseBody::Text(text) => text.hash(&mut hasher),
692 ResponseBody::Bytes(bytes) => bytes.hash(&mut hasher),
693 ResponseBody::Json(value) => {
694 if let Ok(json_string) = serde_json::to_string(value) {
696 json_string.hash(&mut hasher);
697 } else {
698 "invalid_json".hash(&mut hasher);
700 }
701 }
702 }
703
704 format!("{:x}", hasher.finish())
705 }
706
707 pub fn conditional(self, request_etag: Option<&str>) -> Self {
709 if let Some(request_etag) = request_etag {
710 let response_etag = self.generate_etag();
711 let response_etag_quoted = format!("\"{}\"", response_etag);
712
713 if request_etag == response_etag_quoted || request_etag == "*" {
714 return ElifResponse::with_status(ElifStatusCode::NOT_MODIFIED);
715 }
716 }
717 self
718 }
719
720 pub fn with_security_headers(mut self) -> HttpResult<Self> {
722 self = self.header("x-content-type-options", "nosniff")?;
723 self = self.header("x-frame-options", "DENY")?;
724 self = self.header("x-xss-protection", "1; mode=block")?;
725 self = self.header("referrer-policy", "strict-origin-when-cross-origin")?;
726 self = self.header("content-security-policy", "default-src 'self'")?;
727 Ok(self)
728 }
729
730 pub fn with_performance_headers(mut self) -> HttpResult<Self> {
732 self = self.header("x-dns-prefetch-control", "on")?;
733 self = self.header("x-powered-by", "elif.rs")?;
734 Ok(self)
735 }
736}
737
738#[derive(Debug, Clone, Copy)]
740pub enum ImageFormat {
741 Png,
742 Jpeg,
743 Gif,
744 WebP,
745 Svg,
746}
747
748impl ElifResponse {
750 pub fn transform_body<F>(mut self, transform: F) -> Self
752 where
753 F: FnOnce(ResponseBody) -> ResponseBody,
754 {
755 self.body = transform(self.body);
756 self
757 }
758
759 pub fn with_headers<I, K, V>(mut self, headers: I) -> HttpResult<Self>
761 where
762 I: IntoIterator<Item = (K, V)>,
763 K: AsRef<str>,
764 V: AsRef<str>,
765 {
766 for (key, value) in headers {
767 self = self.header(key, value)?;
768 }
769 Ok(self)
770 }
771
772 pub fn build_axum(self) -> Response<axum::body::Body> {
774 match self.build() {
775 Ok(response) => response,
776 Err(e) => {
777 (
779 ElifStatusCode::INTERNAL_SERVER_ERROR.to_axum(),
780 format!("Response build failed: {}", e),
781 )
782 .into_response()
783 }
784 }
785 }
786
787 pub fn is_error(&self) -> bool {
789 self.status.as_u16() >= 400
790 }
791
792 pub fn is_success(&self) -> bool {
794 let status_code = self.status.as_u16();
795 (200..300).contains(&status_code)
796 }
797
798 pub fn is_redirect(&self) -> bool {
800 let status_code = self.status.as_u16();
801 (300..400).contains(&status_code)
802 }
803
804 pub fn body_size_estimate(&self) -> usize {
806 match &self.body {
807 ResponseBody::Empty => 0,
808 ResponseBody::Text(text) => text.len(),
809 ResponseBody::Bytes(bytes) => bytes.len(),
810 ResponseBody::Json(value) => {
811 serde_json::to_string(value).map(|s| s.len()).unwrap_or(0)
813 }
814 }
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821 use serde_json::json;
822
823 #[test]
824 fn test_basic_response_building() {
825 let response = ElifResponse::ok().text("Hello, World!");
826
827 assert_eq!(response.status, ElifStatusCode::OK);
828 match response.body {
829 ResponseBody::Text(text) => assert_eq!(text, "Hello, World!"),
830 _ => panic!("Expected text body"),
831 }
832 }
833
834 #[test]
835 fn test_json_response() {
836 let data = json!({
837 "name": "John Doe",
838 "age": 30
839 });
840
841 let response = ElifResponse::ok().json_value(data.clone());
842
843 match response.body {
844 ResponseBody::Json(value) => assert_eq!(value, data),
845 _ => panic!("Expected JSON body"),
846 }
847 }
848
849 #[test]
850 fn test_status_codes() {
851 assert_eq!(ElifResponse::created().status, ElifStatusCode::CREATED);
852 assert_eq!(ElifResponse::not_found().status, ElifStatusCode::NOT_FOUND);
853 assert_eq!(
854 ElifResponse::internal_server_error().status,
855 ElifStatusCode::INTERNAL_SERVER_ERROR
856 );
857 }
858
859 #[test]
860 fn test_headers() {
861 let response = ElifResponse::ok()
862 .header("x-custom-header", "test-value")
863 .unwrap();
864
865 let custom_header =
866 crate::response::headers::ElifHeaderName::from_str("x-custom-header").unwrap();
867 assert!(response.headers.contains_key(&custom_header));
868 assert_eq!(
869 response.headers.get(&custom_header).unwrap(),
870 &crate::response::headers::ElifHeaderValue::from_static("test-value")
871 );
872 }
873
874 #[test]
875 fn test_redirect_responses() {
876 let redirect = ElifResponse::redirect_permanent("/new-location").unwrap();
877 assert_eq!(redirect.status, ElifStatusCode::MOVED_PERMANENTLY);
878 assert!(redirect.headers.contains_key(
879 &crate::response::headers::ElifHeaderName::from_str("location").unwrap()
880 ));
881 }
882
883 #[test]
884 fn test_status_code_getter() {
885 let response = ElifResponse::created();
886 assert_eq!(response.status_code(), ElifStatusCode::CREATED);
887 }
888
889 #[test]
890 fn test_borrowing_api_headers() {
891 let mut response = ElifResponse::ok();
892
893 response
895 .add_header("x-custom-header", "test-value")
896 .unwrap();
897 response.set_content_type("application/json").unwrap();
898
899 let built_response = response.build().unwrap();
900 let headers = built_response.headers();
901
902 assert!(headers.contains_key("x-custom-header"));
903 assert_eq!(headers.get("x-custom-header").unwrap(), "test-value");
904 assert_eq!(headers.get("content-type").unwrap(), "application/json");
905 }
906
907 #[test]
908 fn test_borrowing_api_body() {
909 let mut response = ElifResponse::ok();
910
911 response.set_text("Hello World");
913 assert_eq!(response.status_code(), ElifStatusCode::OK);
914 match &response.body {
915 ResponseBody::Text(text) => assert_eq!(text, "Hello World"),
916 _ => panic!("Expected text body after calling set_text"),
917 }
918
919 let bytes_data = Bytes::from("binary data");
921 response.set_bytes(bytes_data.clone());
922 match &response.body {
923 ResponseBody::Bytes(bytes) => assert_eq!(bytes, &bytes_data),
924 _ => panic!("Expected bytes body after calling set_bytes"),
925 }
926
927 let data = json!({"message": "Hello"});
929 response.set_json_value(data.clone());
930
931 match &response.body {
933 ResponseBody::Json(value) => assert_eq!(*value, data),
934 _ => panic!("Expected JSON body after calling set_json_value"),
935 }
936 }
937
938 #[test]
939 fn test_borrowing_api_status() {
940 let mut response = ElifResponse::ok();
941
942 response.set_status(ElifStatusCode::CREATED);
944 assert_eq!(response.status_code(), ElifStatusCode::CREATED);
945
946 response.set_status(ElifStatusCode::ACCEPTED);
948 response.set_text("Updated");
949
950 assert_eq!(response.status_code(), ElifStatusCode::ACCEPTED);
951 }
952
953 #[test]
954 fn test_borrowing_api_middleware_pattern() {
955 let mut response = ElifResponse::ok().text("Original");
957
958 let headers = vec![
960 ("x-middleware-1", "executed"),
961 ("x-middleware-2", "processed"),
962 ("x-custom", "value"),
963 ];
964
965 for (name, value) in headers {
966 response.add_header(name, value).unwrap();
968 }
969
970 let built = response.build().unwrap();
971 let response_headers = built.headers();
972
973 assert!(response_headers.contains_key("x-middleware-1"));
974 assert!(response_headers.contains_key("x-middleware-2"));
975 assert!(response_headers.contains_key("x-custom"));
976 }
977
978 #[test]
979 fn test_etag_generation_includes_body_content() {
980 let response1 = ElifResponse::ok().with_text("Hello World");
984 let response2 = ElifResponse::ok().with_text("Different Content");
985
986 let etag1 = response1.generate_etag();
987 let etag2 = response2.generate_etag();
988
989 assert_ne!(
990 etag1, etag2,
991 "ETags should be different for different text content"
992 );
993
994 let json1 = serde_json::json!({"name": "Alice", "age": 30});
996 let json2 = serde_json::json!({"name": "Bob", "age": 25});
997
998 let response3 = ElifResponse::ok().with_json(&json1);
999 let response4 = ElifResponse::ok().with_json(&json2);
1000
1001 let etag3 = response3.generate_etag();
1002 let etag4 = response4.generate_etag();
1003
1004 assert_ne!(
1005 etag3, etag4,
1006 "ETags should be different for different JSON content"
1007 );
1008
1009 let response5 = ElifResponse::ok().with_text("Hello World");
1011 let etag5 = response5.generate_etag();
1012
1013 assert_eq!(
1014 etag1, etag5,
1015 "ETags should be identical for identical content"
1016 );
1017
1018 let response6 = ElifResponse::ok().with_json(&serde_json::json!("Hello World"));
1020 let etag6 = response6.generate_etag();
1021
1022 assert_ne!(
1023 etag1, etag6,
1024 "ETags should be different for different body types even with same content"
1025 );
1026 }
1027
1028 #[test]
1029 fn test_etag_generation_different_body_types() {
1030 let empty_response = ElifResponse::ok();
1033 let text_response = ElifResponse::ok().with_text("test content");
1034 let bytes_response = ElifResponse::ok().bytes(Bytes::from("test content"));
1035 let json_response = ElifResponse::ok().with_json(&serde_json::json!({"key": "value"}));
1036
1037 let empty_etag = empty_response.generate_etag();
1038 let text_etag = text_response.generate_etag();
1039 let bytes_etag = bytes_response.generate_etag();
1040 let json_etag = json_response.generate_etag();
1041
1042 let etags = vec![&empty_etag, &text_etag, &bytes_etag, &json_etag];
1044 for i in 0..etags.len() {
1045 for j in (i + 1)..etags.len() {
1046 assert_ne!(
1047 etags[i], etags[j],
1048 "ETags should be unique for different body types: {} vs {}",
1049 etags[i], etags[j]
1050 );
1051 }
1052 }
1053
1054 let text_response2 = ElifResponse::ok().with_text("test content");
1056 let text_etag2 = text_response2.generate_etag();
1057 assert_eq!(
1058 text_etag, text_etag2,
1059 "ETags should be consistent for identical text content"
1060 );
1061 }
1062
1063 #[test]
1064 fn test_etag_generation_with_status_and_headers() {
1065 let base_content = "same content";
1068
1069 let response_200 = ElifResponse::ok().with_text(base_content);
1071 let response_201 = ElifResponse::created().with_text(base_content);
1072
1073 let etag_200 = response_200.generate_etag();
1074 let etag_201 = response_201.generate_etag();
1075
1076 assert_ne!(
1077 etag_200, etag_201,
1078 "ETags should be different for different status codes"
1079 );
1080
1081 let response_no_header = ElifResponse::ok().with_text(base_content);
1083 let response_with_header = ElifResponse::ok()
1084 .with_text(base_content)
1085 .with_header("x-custom", "value");
1086
1087 let etag_no_header = response_no_header.generate_etag();
1088 let etag_with_header = response_with_header.generate_etag();
1089
1090 assert_ne!(
1091 etag_no_header, etag_with_header,
1092 "ETags should be different when headers differ"
1093 );
1094 }
1095
1096 #[test]
1097 fn test_etag_conditional_response() {
1098 let response = ElifResponse::ok().with_text("Test content");
1099 let etag = response.generate_etag();
1100 let etag_quoted = format!("\"{}\"", etag);
1101
1102 let conditional_response = response.conditional(Some(&etag_quoted));
1104 assert_eq!(
1105 conditional_response.status_code(),
1106 ElifStatusCode::NOT_MODIFIED
1107 );
1108
1109 let response2 = ElifResponse::ok().with_text("Test content");
1111 let different_etag = "\"different_etag_value\"";
1112 let conditional_response2 = response2.conditional(Some(different_etag));
1113 assert_eq!(conditional_response2.status_code(), ElifStatusCode::OK);
1114
1115 let wildcard_response = ElifResponse::ok()
1117 .with_text("Test content")
1118 .conditional(Some("*"));
1119 assert_eq!(
1120 wildcard_response.status_code(),
1121 ElifStatusCode::NOT_MODIFIED
1122 );
1123 }
1124}