Skip to main content

httpward_core/error/
error_handler.rs

1use rama::http::{Response, Body, StatusCode};
2use std::fs;
3use std::path::Path;
4
5#[derive(Clone, Debug)]
6pub struct ErrorHandler {
7    template_content: &'static [u8],
8}
9
10impl ErrorHandler {
11    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
12        let template_path = Path::new("httpward-core/assets/error.html");
13        let template_content = fs::read(template_path)
14            .map_err(|e| format!("Failed to read error template: {}", e))?;
15        
16        // Convert Vec<u8> to Box<[u8]> and then leak to get &'static [u8]
17        let static_content = Box::leak(template_content.into_boxed_slice());
18        
19        Ok(Self { template_content: static_content })
20    }
21
22    pub fn create_error_response(&self, status: StatusCode, title: &str, description: &str) -> Result<Response<Body>, Box<dyn std::error::Error>> {
23        let status_code = status.as_u16();
24        let template_str = std::str::from_utf8(self.template_content)
25            .map_err(|e| format!("Invalid UTF-8 in template: {}", e))?;
26        
27        let content = template_str
28            .replace("{{e_num}}", &status_code.to_string())
29            .replace("{{e_text}}", title)
30            .replace("{{e_desc}}", description);
31
32        Ok(Response::builder()
33            .status(status)
34            .header("content-type", "text/html; charset=utf-8")
35            .body(Body::from(content))
36            .unwrap())
37    }
38
39    pub fn create_error_response_with_code(&self, status: StatusCode) -> Result<Response<Body>, Box<dyn std::error::Error>> {
40        let (title, description) = match status {
41            StatusCode::NOT_FOUND => (
42                "Page Not Found",
43                "The requested page could not be found on this server."
44            ),
45            StatusCode::INTERNAL_SERVER_ERROR => (
46                "Internal Server Error",
47                "An unexpected error occurred while processing your request."
48            ),
49            StatusCode::BAD_GATEWAY => (
50                "Bad Gateway",
51                "The server encountered an error while trying to proxy your request."
52            ),
53            StatusCode::FORBIDDEN => (
54                "Access Forbidden",
55                "You do not have permission to access this resource."
56            ),
57            StatusCode::UNAUTHORIZED => (
58                "Unauthorized",
59                "Authentication is required to access this resource."
60            ),
61            StatusCode::BAD_REQUEST => (
62                "Bad Request",
63                "The server cannot process your request due to invalid syntax."
64            ),
65            _ => (
66                "Error",
67                "An error occurred while processing your request."
68            ),
69        };
70
71        self.create_error_response(status, title, description)
72    }
73}
74
75impl Default for ErrorHandler {
76    fn default() -> Self {
77        Self {
78            template_content: include_bytes!("../../assets/error.html"),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use rama::http::StatusCode;
87
88    #[test]
89    fn test_error_handler_creation() {
90        let handler = ErrorHandler::default();
91        assert!(!handler.template_content.is_empty());
92        let template_str = std::str::from_utf8(handler.template_content).unwrap();
93        assert!(template_str.contains("{{e_num}}"));
94        assert!(template_str.contains("{{e_text}}"));
95        assert!(template_str.contains("{{e_desc}}"));
96    }
97
98    #[test]
99    fn test_404_error_response() {
100        let handler = ErrorHandler::default();
101        let response = handler.create_error_response_with_code(StatusCode::NOT_FOUND).unwrap();
102        
103        assert_eq!(response.status(), StatusCode::NOT_FOUND);
104        assert_eq!(response.headers().get("content-type").unwrap(), "text/html; charset=utf-8");
105    }
106
107    #[test]
108    fn test_500_error_response() {
109        let handler = ErrorHandler::default();
110        let response = handler.create_error_response_with_code(StatusCode::INTERNAL_SERVER_ERROR).unwrap();
111        
112        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
113    }
114
115    #[test]
116    fn test_custom_error_response() {
117        let handler = ErrorHandler::default();
118        let response = handler.create_error_response(
119            StatusCode::BAD_REQUEST, 
120            "Custom Error", 
121            "This is a custom error description"
122        ).unwrap();
123        
124        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
125    }
126
127    #[test]
128    fn test_template_replacement() {
129        let handler = ErrorHandler::default();
130        let template_str = std::str::from_utf8(handler.template_content).unwrap();
131        
132        // Test that template contains placeholders
133        assert!(template_str.contains("{{e_num}}"));
134        assert!(template_str.contains("{{e_text}}"));
135        assert!(template_str.contains("{{e_desc}}"));
136        
137        // Test replacement logic
138        let result = template_str
139            .replace("{{e_num}}", "404")
140            .replace("{{e_text}}", "Not Found")
141            .replace("{{e_desc}}", "Page not found");
142        
143        assert!(result.contains("404"));
144        assert!(result.contains("Not Found"));
145        assert!(result.contains("Page not found"));
146        assert!(!result.contains("{{e_num}}"));
147        assert!(!result.contains("{{e_text}}"));
148        assert!(!result.contains("{{e_desc}}"));
149    }
150}