elif_http/middleware/core/
error_handler.rs

1//! Error handling middleware for HTTP requests
2//! 
3//! Provides comprehensive error handling including panic recovery and error response formatting.
4
5use crate::{
6    errors::HttpError,
7    middleware::v2::{Middleware, Next, NextFuture},
8    request::ElifRequest,
9    response::IntoElifResponse,
10};
11use futures_util::future::FutureExt;
12
13/// Error handling middleware configuration
14#[derive(Debug, Clone)]
15pub struct ErrorHandlerConfig {
16    /// Whether to include panic details in error responses (development only)
17    pub include_panic_details: bool,
18    
19    /// Whether to log errors
20    pub log_errors: bool,
21}
22
23impl Default for ErrorHandlerConfig {
24    fn default() -> Self {
25        Self {
26            include_panic_details: cfg!(debug_assertions), // Only in debug builds
27            log_errors: true,
28        }
29    }
30}
31
32/// Error handling middleware
33#[derive(Debug)]
34pub struct ErrorHandlerMiddleware {
35    config: ErrorHandlerConfig,
36}
37
38impl ErrorHandlerMiddleware {
39    /// Create new error handling middleware with default config
40    pub fn new() -> Self {
41        Self {
42            config: ErrorHandlerConfig::default(),
43        }
44    }
45
46    /// Create with custom configuration
47    pub fn with_config(config: ErrorHandlerConfig) -> Self {
48        Self { config }
49    }
50
51    /// Enable panic details in responses (use only in development)
52    pub fn with_panic_details(mut self, include: bool) -> Self {
53        self.config.include_panic_details = include;
54        self
55    }
56
57    /// Enable error logging
58    pub fn with_logging(mut self, enable: bool) -> Self {
59        self.config.log_errors = enable;
60        self
61    }
62
63}
64
65impl Default for ErrorHandlerMiddleware {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl Middleware for ErrorHandlerMiddleware {
72    fn handle(&self, request: ElifRequest, next: Next) -> NextFuture<'static> {
73        let config = self.config.clone();
74        Box::pin(async move {
75            // The future from `next.run()` might panic, so we catch it.
76            // `AssertUnwindSafe` is used because the handler might not be `UnwindSafe`.
77            let result = std::panic::AssertUnwindSafe(next.run(request))
78                .catch_unwind()
79                .await;
80
81            match result {
82                Ok(response) => response,
83                Err(panic_info) => {
84                    // A panic occurred, so we create an error response.
85                    let panic_message = if let Some(s) = panic_info.downcast_ref::<String>() {
86                        s.clone()
87                    } else if let Some(s) = panic_info.downcast_ref::<&str>() {
88                        s.to_string()
89                    } else {
90                        "Unknown panic occurred".to_string()
91                    };
92
93                    if config.log_errors {
94                        // Using tracing::error for logging panics.
95                        tracing::error!("Panic in request handler: {}", panic_message);
96                    }
97
98                    let error_message = if config.include_panic_details {
99                        format!("Internal server error: {}", panic_message)
100                    } else {
101                        "Internal server error occurred".to_string()
102                    };
103
104                    let http_error = HttpError::internal(error_message);
105                    http_error.into_response()
106                }
107            }
108        })
109    }
110
111    fn name(&self) -> &'static str {
112        "ErrorHandlerMiddleware"
113    }
114}
115
116/// Helper function to create error handler middleware
117pub fn error_handler() -> ErrorHandlerMiddleware {
118    ErrorHandlerMiddleware::new()
119}
120
121/// Helper function to create error handler middleware with config
122pub fn error_handler_with_config(config: ErrorHandlerConfig) -> ErrorHandlerMiddleware {
123    ErrorHandlerMiddleware::with_config(config)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::{
130        middleware::v2::MiddlewarePipelineV2,
131        request::ElifMethod,
132        response::{ElifResponse, headers::ElifHeaderMap},
133    };
134
135    #[tokio::test]
136    async fn test_error_handler_config() {
137        let config = ErrorHandlerConfig {
138            include_panic_details: true,
139            log_errors: false,
140        };
141
142        assert!(config.include_panic_details);
143        assert!(!config.log_errors);
144    }
145
146    #[tokio::test]
147    async fn test_error_handler_middleware_creation() {
148        let middleware = ErrorHandlerMiddleware::new()
149            .with_panic_details(true)
150            .with_logging(false);
151
152        assert!(middleware.config.include_panic_details);
153        assert!(!middleware.config.log_errors);
154    }
155
156    #[tokio::test]
157    async fn test_error_handler_with_http_error() {
158        let middleware = ErrorHandlerMiddleware::new();
159        
160        let request = ElifRequest::new(
161            ElifMethod::GET,
162            "/test".parse().unwrap(),
163            ElifHeaderMap::new(),
164        );
165        
166        let next = crate::middleware::v2::Next::new(|_req| {
167            Box::pin(async {
168                // Return an error response
169                HttpError::bad_request("Test error").into_response()
170            })
171        });
172        
173        let response = middleware.handle(request, next).await;
174        assert_eq!(response.status_code(), crate::response::status::ElifStatusCode::BAD_REQUEST);
175    }
176
177    #[tokio::test]
178    async fn test_error_handler_normal_flow() {
179        let middleware = ErrorHandlerMiddleware::new();
180        let pipeline = MiddlewarePipelineV2::new().add(middleware);
181        
182        let request = ElifRequest::new(
183            ElifMethod::GET,
184            "/test".parse().unwrap(),
185            ElifHeaderMap::new(),
186        );
187        
188        let response = pipeline.execute(request, |_req| {
189            Box::pin(async {
190                ElifResponse::ok().json_value(serde_json::json!({
191                    "message": "Success"
192                }))
193            })
194        }).await;
195        
196        assert_eq!(response.status_code(), crate::response::status::ElifStatusCode::OK);
197    }
198
199    #[test]
200    fn test_error_handler_config_default() {
201        let config = ErrorHandlerConfig::default();
202        
203        // Should include panic details only in debug mode
204        assert_eq!(config.include_panic_details, cfg!(debug_assertions));
205        assert!(config.log_errors);
206    }
207
208
209}