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
64impl Default for ErrorHandlerMiddleware {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Middleware for ErrorHandlerMiddleware {
71 fn handle(&self, request: ElifRequest, next: Next) -> NextFuture<'static> {
72 let config = self.config.clone();
73 Box::pin(async move {
74 let result = std::panic::AssertUnwindSafe(next.run(request))
77 .catch_unwind()
78 .await;
79
80 match result {
81 Ok(response) => response,
82 Err(panic_info) => {
83 let panic_message = if let Some(s) = panic_info.downcast_ref::<String>() {
85 s.clone()
86 } else if let Some(s) = panic_info.downcast_ref::<&str>() {
87 s.to_string()
88 } else {
89 "Unknown panic occurred".to_string()
90 };
91
92 if config.log_errors {
93 tracing::error!("Panic in request handler: {}", panic_message);
95 }
96
97 let error_message = if config.include_panic_details {
98 format!("Internal server error: {}", panic_message)
99 } else {
100 "Internal server error occurred".to_string()
101 };
102
103 let http_error = HttpError::internal(error_message);
104 http_error.into_response()
105 }
106 }
107 })
108 }
109
110 fn name(&self) -> &'static str {
111 "ErrorHandlerMiddleware"
112 }
113}
114
115pub fn error_handler() -> ErrorHandlerMiddleware {
117 ErrorHandlerMiddleware::new()
118}
119
120pub fn error_handler_with_config(config: ErrorHandlerConfig) -> ErrorHandlerMiddleware {
122 ErrorHandlerMiddleware::with_config(config)
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::{
129 middleware::v2::MiddlewarePipelineV2,
130 request::ElifMethod,
131 response::{headers::ElifHeaderMap, ElifResponse},
132 };
133
134 #[tokio::test]
135 async fn test_error_handler_config() {
136 let config = ErrorHandlerConfig {
137 include_panic_details: true,
138 log_errors: false,
139 };
140
141 assert!(config.include_panic_details);
142 assert!(!config.log_errors);
143 }
144
145 #[tokio::test]
146 async fn test_error_handler_middleware_creation() {
147 let middleware = ErrorHandlerMiddleware::new()
148 .with_panic_details(true)
149 .with_logging(false);
150
151 assert!(middleware.config.include_panic_details);
152 assert!(!middleware.config.log_errors);
153 }
154
155 #[tokio::test]
156 async fn test_error_handler_with_http_error() {
157 let middleware = ErrorHandlerMiddleware::new();
158
159 let request = ElifRequest::new(
160 ElifMethod::GET,
161 "/test".parse().unwrap(),
162 ElifHeaderMap::new(),
163 );
164
165 let next = crate::middleware::v2::Next::new(|_req| {
166 Box::pin(async {
167 HttpError::bad_request("Test error").into_response()
169 })
170 });
171
172 let response = middleware.handle(request, next).await;
173 assert_eq!(
174 response.status_code(),
175 crate::response::status::ElifStatusCode::BAD_REQUEST
176 );
177 }
178
179 #[tokio::test]
180 async fn test_error_handler_normal_flow() {
181 let middleware = ErrorHandlerMiddleware::new();
182 let pipeline = MiddlewarePipelineV2::new().add(middleware);
183
184 let request = ElifRequest::new(
185 ElifMethod::GET,
186 "/test".parse().unwrap(),
187 ElifHeaderMap::new(),
188 );
189
190 let response = pipeline
191 .execute(request, |_req| {
192 Box::pin(async {
193 ElifResponse::ok().json_value(serde_json::json!({
194 "message": "Success"
195 }))
196 })
197 })
198 .await;
199
200 assert_eq!(
201 response.status_code(),
202 crate::response::status::ElifStatusCode::OK
203 );
204 }
205
206 #[test]
207 fn test_error_handler_config_default() {
208 let config = ErrorHandlerConfig::default();
209
210 assert_eq!(config.include_panic_details, cfg!(debug_assertions));
212 assert!(config.log_errors);
213 }
214}