elif_http/middleware/core/
error_handler.rs1use crate::{
6 errors::HttpError,
7 middleware::v2::{Middleware, Next, NextFuture},
8 request::ElifRequest,
9 response::IntoElifResponse,
10};
11use futures_util::future::FutureExt;
12
13#[derive(Debug, Clone)]
15pub struct ErrorHandlerConfig {
16 pub include_panic_details: bool,
18
19 pub log_errors: bool,
21}
22
23impl Default for ErrorHandlerConfig {
24 fn default() -> Self {
25 Self {
26 include_panic_details: cfg!(debug_assertions), log_errors: true,
28 }
29 }
30}
31
32#[derive(Debug)]
34pub struct ErrorHandlerMiddleware {
35 config: ErrorHandlerConfig,
36}
37
38impl ErrorHandlerMiddleware {
39 pub fn new() -> Self {
41 Self {
42 config: ErrorHandlerConfig::default(),
43 }
44 }
45
46 pub fn with_config(config: ErrorHandlerConfig) -> Self {
48 Self { config }
49 }
50
51 pub fn with_panic_details(mut self, include: bool) -> Self {
53 self.config.include_panic_details = include;
54 self
55 }
56
57 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 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 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 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
116pub fn error_handler() -> ErrorHandlerMiddleware {
118 ErrorHandlerMiddleware::new()
119}
120
121pub 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 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 assert_eq!(config.include_panic_details, cfg!(debug_assertions));
205 assert!(config.log_errors);
206 }
207
208
209}