1use hyper::StatusCode;
7use reinhardt_core::signals::{
8 RequestFinishedEvent, RequestStartedEvent, request_finished, request_started,
9};
10use reinhardt_http::Handler;
11use reinhardt_http::{Request, Response};
12use reinhardt_urls::routers::DefaultRouter;
13use std::sync::Arc;
14use tracing::{debug, error, trace, warn};
15
16use crate::{DispatchError, exception::exception_to_dispatch_error};
17
18pub struct BaseHandler {
23 #[allow(dead_code)]
31 is_async: bool,
32 router: Option<Arc<DefaultRouter>>,
33}
34
35impl BaseHandler {
36 pub fn new() -> Self {
38 Self {
39 is_async: true,
40 router: None,
41 }
42 }
43
44 pub fn with_router(router: Arc<DefaultRouter>) -> Self {
58 Self {
59 is_async: true,
60 router: Some(router),
61 }
62 }
63
64 pub async fn handle_request(
72 &self,
73 request: Request,
74 ) -> std::result::Result<Response, DispatchError> {
75 match self.handle_request_with_errors(request).await {
76 Err(DispatchError::UrlResolution(_)) => Ok(Response::new(StatusCode::NOT_FOUND)),
77 response => response,
78 }
79 }
80
81 async fn handle_request_with_errors(
84 &self,
85 request: Request,
86 ) -> std::result::Result<Response, DispatchError> {
87 self.handle_request_with_framework_errors(request)
88 .await
89 .map_err(exception_to_dispatch_error)
90 }
91
92 async fn handle_request_with_framework_errors(
98 &self,
99 request: Request,
100 ) -> reinhardt_core::exception::Result<Response> {
101 trace!("Handling request: {:?}", request.uri);
102
103 let event = RequestStartedEvent::new();
105 if let Err(e) = request_started().send(event).await {
106 warn!("Failed to send request_started signal: {}", e);
107 }
108
109 let response = Self::get_response_async(request, self.router.as_ref()).await;
111
112 let event = RequestFinishedEvent::new();
114 if let Err(e) = request_finished().send(event).await {
115 warn!("Failed to send request_finished signal: {}", e);
116 }
117
118 response
119 }
120
121 async fn get_response_async(
129 request: Request,
130 router: Option<&Arc<DefaultRouter>>,
131 ) -> reinhardt_core::exception::Result<Response> {
132 debug!("Getting response for: {}", request.uri.path());
133
134 if let Some(router) = router {
136 trace!("Attempting to route request through router");
137
138 match router.handle(request).await {
140 Ok(response) => {
141 trace!("Route handled successfully");
142 return Ok(response);
143 }
144 Err(reinhardt_core::exception::Error::NotFound(msg)) => {
145 debug!("No route matched: {}", msg);
146 return Err(reinhardt_core::exception::Error::NotFound(msg));
147 }
148 Err(e) => {
149 error!("Handler error: {}", e);
150 return Err(e);
153 }
154 }
155 }
156
157 debug!("No router configured, returning a URL resolution error");
159 Err(reinhardt_core::exception::Error::NotFound(
160 "No router configured".to_owned(),
161 ))
162 }
163
164 pub async fn handle_exception(&self, _request: &Request, error: DispatchError) -> Response {
169 error!("Handling exception: {}", error);
170
171 crate::build_error_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
172 }
173
174 pub fn is_async(&self) -> bool {
176 self.is_async
177 }
178
179 pub fn set_async(&mut self, is_async: bool) {
181 self.is_async = is_async;
182 }
183}
184
185impl Default for BaseHandler {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191#[async_trait::async_trait]
192impl Handler for BaseHandler {
193 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
194 let has_exception_handler = request
195 .extensions
196 .contains::<Arc<dyn reinhardt_http::ExceptionHandler>>();
197 if has_exception_handler {
198 return self.handle_request_with_framework_errors(request).await;
199 }
200 match self.handle_request_with_errors(request).await {
201 Ok(response) => Ok(response),
202 Err(DispatchError::UrlResolution(_)) => Ok(Response::new(StatusCode::NOT_FOUND)),
203 Err(e) => {
204 error!("Handler error in BaseHandler::handle: {}", e);
206 Ok(crate::build_error_response(
207 StatusCode::INTERNAL_SERVER_ERROR,
208 "Internal Server Error",
209 ))
210 }
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use std::sync::Mutex;
218
219 use super::*;
220 use async_trait::async_trait;
221 use bytes::Bytes;
222 use hyper::{HeaderMap, Method, Version};
223 use reinhardt_http::{ExceptionHandler, ExceptionHandlingHandler, Middleware, MiddlewareChain};
224 use reinhardt_urls::routers::{DefaultRouter, Router, path};
225 use rstest::rstest;
226
227 struct TestHandler {
229 response_body: String,
230 }
231
232 #[async_trait]
233 impl Handler for TestHandler {
234 async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
235 Ok(Response::ok().with_body(self.response_body.clone()))
236 }
237 }
238
239 #[tokio::test]
240 async fn test_base_handler_new() {
241 let handler = BaseHandler::new();
242 assert!(handler.is_async());
243 }
244
245 #[tokio::test]
246 async fn test_base_handler_handle_request() {
247 let handler = BaseHandler::new();
248 let request = Request::builder()
249 .method(Method::GET)
250 .uri("/")
251 .version(Version::HTTP_11)
252 .headers(HeaderMap::new())
253 .body(Bytes::new())
254 .build()
255 .unwrap();
256
257 let response = handler.handle_request(request).await;
258 let resp = response.unwrap();
259 assert_eq!(resp.status, StatusCode::NOT_FOUND);
261 }
262
263 #[tokio::test]
264 async fn test_base_handler_handle_exception() {
265 let handler = BaseHandler::new();
266 let request = Request::builder()
267 .method(Method::GET)
268 .uri("/")
269 .version(Version::HTTP_11)
270 .headers(HeaderMap::new())
271 .body(Bytes::new())
272 .build()
273 .unwrap();
274 let error = DispatchError::View("Test error".to_string());
275
276 let response = handler.handle_exception(&request, error).await;
277 assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
278 }
279
280 #[tokio::test]
285 async fn test_handle_exception_does_not_expose_internal_details() {
286 let handler = BaseHandler::new();
288 let request = Request::builder()
289 .method(Method::GET)
290 .uri("/")
291 .version(Version::HTTP_11)
292 .headers(HeaderMap::new())
293 .body(Bytes::new())
294 .build()
295 .unwrap();
296 let sensitive_detail = "database connection refused at postgres://admin:secret@db:5432";
297 let error = DispatchError::Internal(sensitive_detail.to_string());
298
299 let response = handler.handle_exception(&request, error).await;
301
302 let body = String::from_utf8(response.body.to_vec()).unwrap();
304 assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
305 assert!(!body.contains("database"));
306 assert!(!body.contains("postgres"));
307 assert!(!body.contains("secret"));
308 assert_eq!(body, "Internal Server Error");
309 }
310
311 #[tokio::test]
312 async fn test_handler_impl_does_not_expose_error_in_body() {
313 struct FailingHandler;
315
316 #[async_trait]
317 impl Handler for FailingHandler {
318 async fn handle(&self, _req: Request) -> reinhardt_core::exception::Result<Response> {
319 Err(reinhardt_core::exception::Error::Internal(
320 "module::secret_handler panicked at /src/app/handlers.rs:42".to_string(),
321 ))
322 }
323 }
324
325 let mut router = DefaultRouter::new();
326 let failing = Arc::new(FailingHandler);
327 let mut route = path("/fail", failing);
328 route.name = Some("fail".to_string());
329 router.add_route(route);
330 let handler = BaseHandler::with_router(Arc::new(router));
331
332 let request = Request::builder()
333 .method(Method::GET)
334 .uri("/fail")
335 .version(Version::HTTP_11)
336 .headers(HeaderMap::new())
337 .body(Bytes::new())
338 .build()
339 .unwrap();
340
341 let response = handler.handle(request).await.unwrap();
343
344 let body = String::from_utf8(response.body.to_vec()).unwrap();
346 assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR);
347 assert!(!body.contains("panicked"));
348 assert!(!body.contains("handlers.rs"));
349 assert!(!body.contains("secret_handler"));
350 assert_eq!(body, "Internal Server Error");
351 }
352
353 struct TeapotExceptionHandler;
354
355 #[async_trait]
356 impl ExceptionHandler for TeapotExceptionHandler {
357 async fn handle_exception(
358 &self,
359 _request: &Request,
360 _error: reinhardt_core::exception::Error,
361 ) -> Response {
362 Response::new(StatusCode::IM_A_TEAPOT).with_body("teapot")
363 }
364 }
365
366 struct StatusExceptionHandler;
367
368 #[async_trait]
369 impl ExceptionHandler for StatusExceptionHandler {
370 async fn handle_exception(
371 &self,
372 _request: &Request,
373 error: reinhardt_core::exception::Error,
374 ) -> Response {
375 let status = StatusCode::from_u16(error.status_code())
376 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
377 Response::new(status)
378 }
379 }
380
381 #[rstest]
382 #[tokio::test]
383 async fn base_handler_routing_errors_reach_http_exception_handler() {
384 let base = Arc::new(BaseHandler::with_router(Arc::new(DefaultRouter::new())));
386 let handler = ExceptionHandlingHandler::new(base, Arc::new(TeapotExceptionHandler));
387 let request = Request::builder()
388 .method(Method::GET)
389 .uri("/missing")
390 .version(Version::HTTP_11)
391 .headers(HeaderMap::new())
392 .body(Bytes::new())
393 .build()
394 .unwrap();
395
396 let response = handler.handle(request).await.unwrap();
398
399 assert_eq!(response.status, StatusCode::IM_A_TEAPOT);
401 assert_eq!(response.body, Bytes::from_static(b"teapot"));
402 }
403
404 #[rstest]
405 #[tokio::test]
406 async fn base_handler_view_errors_reach_http_exception_handler() {
407 struct FailingHandler;
408
409 #[async_trait]
410 impl Handler for FailingHandler {
411 async fn handle(
412 &self,
413 _request: Request,
414 ) -> reinhardt_core::exception::Result<Response> {
415 Err(reinhardt_core::exception::Error::Internal(
416 "view failed".to_owned(),
417 ))
418 }
419 }
420
421 let mut router = DefaultRouter::new();
423 router.add_route(path("/fail", Arc::new(FailingHandler)));
424 let base = Arc::new(BaseHandler::with_router(Arc::new(router)));
425 let handler = ExceptionHandlingHandler::new(base, Arc::new(TeapotExceptionHandler));
426 let request = Request::builder()
427 .method(Method::GET)
428 .uri("/fail")
429 .version(Version::HTTP_11)
430 .headers(HeaderMap::new())
431 .body(Bytes::new())
432 .build()
433 .unwrap();
434
435 let response = handler.handle(request).await.unwrap();
437
438 assert_eq!(response.status, StatusCode::IM_A_TEAPOT);
440 assert_eq!(response.body, Bytes::from_static(b"teapot"));
441 }
442
443 #[rstest]
444 #[tokio::test]
445 async fn base_handler_preserves_endpoint_error_status_for_http_exception_handler() {
446 struct AuthenticationHandler;
447
448 #[async_trait]
449 impl Handler for AuthenticationHandler {
450 async fn handle(
451 &self,
452 _request: Request,
453 ) -> reinhardt_core::exception::Result<Response> {
454 Err(reinhardt_core::exception::Error::Authentication(
455 "credentials rejected".to_owned(),
456 ))
457 }
458 }
459
460 let mut router = DefaultRouter::new();
462 router.add_route(path("/private", Arc::new(AuthenticationHandler)));
463 let base = Arc::new(BaseHandler::with_router(Arc::new(router)));
464 let handler = ExceptionHandlingHandler::new(base, Arc::new(StatusExceptionHandler));
465 let request = Request::builder()
466 .method(Method::GET)
467 .uri("/private")
468 .version(Version::HTTP_11)
469 .headers(HeaderMap::new())
470 .body(Bytes::new())
471 .build()
472 .unwrap();
473
474 let response = handler.handle(request).await.unwrap();
476
477 assert_eq!(response.status, StatusCode::UNAUTHORIZED);
479 }
480
481 #[rstest]
482 #[tokio::test]
483 async fn base_handler_exception_handler_receives_resolved_path_params() {
484 struct FailingHandler;
485
486 #[async_trait]
487 impl Handler for FailingHandler {
488 async fn handle(
489 &self,
490 _request: Request,
491 ) -> reinhardt_core::exception::Result<Response> {
492 Err(reinhardt_core::exception::Error::Internal(
493 "view failed".to_owned(),
494 ))
495 }
496 }
497
498 struct PassthroughMiddleware;
499
500 #[async_trait]
501 impl Middleware for PassthroughMiddleware {
502 async fn process(
503 &self,
504 request: Request,
505 next: Arc<dyn Handler>,
506 ) -> reinhardt_core::exception::Result<Response> {
507 next.handle(request).await
508 }
509 }
510
511 struct PathParamExceptionHandler {
512 observed: Arc<Mutex<Option<String>>>,
513 }
514
515 #[async_trait]
516 impl ExceptionHandler for PathParamExceptionHandler {
517 async fn handle_exception(
518 &self,
519 request: &Request,
520 _error: reinhardt_core::exception::Error,
521 ) -> Response {
522 *self.observed.lock().unwrap() = request.path_params.get("id").cloned();
523 Response::new(StatusCode::IM_A_TEAPOT)
524 }
525 }
526
527 let observed = Arc::new(Mutex::new(None));
529 let mut router = DefaultRouter::new();
530 let mut route = path("/items/{id}", Arc::new(FailingHandler));
531 route.name = Some("item".to_owned());
532 router.add_route(route);
533 let base = Arc::new(BaseHandler::with_router(Arc::new(router)));
534 let handler = MiddlewareChain::new(base)
535 .with_middleware(Arc::new(PassthroughMiddleware))
536 .with_exception_handler(Arc::new(PathParamExceptionHandler {
537 observed: Arc::clone(&observed),
538 }));
539 let request = Request::builder()
540 .method(Method::GET)
541 .uri("/items/42")
542 .version(Version::HTTP_11)
543 .headers(HeaderMap::new())
544 .body(Bytes::new())
545 .build()
546 .unwrap();
547
548 let response = handler.handle(request).await.unwrap();
550
551 assert_eq!(response.status, StatusCode::IM_A_TEAPOT);
553 assert_eq!(*observed.lock().unwrap(), Some("42".to_owned()));
554 }
555
556 #[rstest]
557 #[tokio::test]
558 async fn middleware_chain_exception_handler_refreshes_path_params_for_all_error_paths() {
559 struct Endpoint {
560 fails: bool,
561 }
562
563 #[async_trait]
564 impl Handler for Endpoint {
565 async fn handle(
566 &self,
567 _request: Request,
568 ) -> reinhardt_core::exception::Result<Response> {
569 if self.fails {
570 Err(reinhardt_core::exception::Error::Internal(
571 "endpoint failed".to_owned(),
572 ))
573 } else {
574 Ok(Response::ok())
575 }
576 }
577 }
578
579 struct RecordingExceptionHandler {
580 observed: Arc<Mutex<Vec<Option<String>>>>,
581 }
582
583 #[async_trait]
584 impl ExceptionHandler for RecordingExceptionHandler {
585 async fn handle_exception(
586 &self,
587 request: &Request,
588 _error: reinhardt_core::exception::Error,
589 ) -> Response {
590 self.observed
591 .lock()
592 .unwrap()
593 .push(request.path_params.get("id").cloned());
594 Response::new(StatusCode::IM_A_TEAPOT)
595 }
596 }
597
598 struct PostProcessingMiddleware {
599 fails: bool,
600 }
601
602 #[async_trait]
603 impl Middleware for PostProcessingMiddleware {
604 async fn process(
605 &self,
606 request: Request,
607 next: Arc<dyn Handler>,
608 ) -> reinhardt_core::exception::Result<Response> {
609 let response = next.handle(request).await?;
610 if self.fails {
611 Err(reinhardt_core::exception::Error::Internal(
612 "middleware failed after next".to_owned(),
613 ))
614 } else {
615 Ok(response)
616 }
617 }
618 }
619
620 let build_base = |fails| {
621 let mut router = DefaultRouter::new();
622 router.add_route(path("/items/{id}", Arc::new(Endpoint { fails })));
623 Arc::new(BaseHandler::with_router(Arc::new(router))) as Arc<dyn Handler>
624 };
625 let request = || {
626 Request::builder()
627 .method(Method::GET)
628 .uri("/items/42")
629 .version(Version::HTTP_11)
630 .headers(HeaderMap::new())
631 .body(Bytes::new())
632 .build()
633 .unwrap()
634 };
635 let observed = Arc::new(Mutex::new(Vec::new()));
636 let exception_handler = || {
637 Arc::new(RecordingExceptionHandler {
638 observed: Arc::clone(&observed),
639 })
640 };
641
642 let bare =
644 MiddlewareChain::new(build_base(true)).with_exception_handler(exception_handler());
645 let bare_response = bare.handle(request()).await.unwrap();
646
647 let single = MiddlewareChain::new(build_base(false))
649 .with_middleware(Arc::new(PostProcessingMiddleware { fails: true }))
650 .with_exception_handler(exception_handler());
651 let single_response = single.handle(request()).await.unwrap();
652
653 let composed = MiddlewareChain::new(build_base(false))
655 .with_middleware(Arc::new(PostProcessingMiddleware { fails: true }))
656 .with_middleware(Arc::new(PostProcessingMiddleware { fails: false }))
657 .with_exception_handler(exception_handler());
658 let composed_response = composed.handle(request()).await.unwrap();
659
660 assert_eq!(bare_response.status, StatusCode::IM_A_TEAPOT);
662 assert_eq!(single_response.status, StatusCode::IM_A_TEAPOT);
663 assert_eq!(composed_response.status, StatusCode::IM_A_TEAPOT);
664 assert_eq!(
665 *observed.lock().unwrap(),
666 vec![
667 Some("42".to_owned()),
668 Some("42".to_owned()),
669 Some("42".to_owned())
670 ]
671 );
672 }
673
674 #[test]
675 fn test_base_handler_async_mode() {
676 let mut handler = BaseHandler::new();
677 assert!(handler.is_async());
678
679 handler.set_async(false);
680 assert!(!handler.is_async());
681 }
682
683 #[tokio::test]
684 async fn test_base_handler_different_methods() {
685 let handler = BaseHandler::new();
686
687 for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] {
688 let request = Request::builder()
689 .method(method)
690 .uri("/")
691 .version(Version::HTTP_11)
692 .headers(HeaderMap::new())
693 .body(Bytes::new())
694 .build()
695 .unwrap();
696
697 let response = handler.handle_request(request).await;
698 assert!(response.is_ok());
699 }
700 }
701
702 #[tokio::test]
703 async fn test_base_handler_different_uris() {
704 let handler = BaseHandler::new();
705
706 for path in ["/", "/test", "/api/v1/users", "/admin/login"] {
707 let request = Request::builder()
708 .method(Method::GET)
709 .uri(path)
710 .version(Version::HTTP_11)
711 .headers(HeaderMap::new())
712 .body(Bytes::new())
713 .build()
714 .unwrap();
715
716 let response = handler.handle_request(request).await;
717 assert!(response.is_ok());
718 }
719 }
720
721 #[tokio::test]
722 async fn test_handler_with_router() {
723 let mut router = DefaultRouter::new();
725 let test_handler = Arc::new(TestHandler {
726 response_body: "Test response".to_string(),
727 });
728 let mut route = path("/test", test_handler);
729 route.name = Some("test".to_string());
730 router.add_route(route);
731
732 let handler = BaseHandler::with_router(Arc::new(router));
734
735 let request = Request::builder()
737 .method(Method::GET)
738 .uri("/test")
739 .version(Version::HTTP_11)
740 .headers(HeaderMap::new())
741 .body(Bytes::new())
742 .build()
743 .unwrap();
744
745 let response = handler.handle_request(request).await;
746 let resp = response.unwrap();
747 assert_eq!(resp.status, StatusCode::OK);
748
749 let body = String::from_utf8(resp.body.to_vec()).unwrap();
750 assert_eq!(body, "Test response");
751 }
752
753 #[tokio::test]
754 async fn test_handler_404_not_found() {
755 let router = DefaultRouter::new();
757
758 let handler = BaseHandler::with_router(Arc::new(router));
760
761 let request = Request::builder()
763 .method(Method::GET)
764 .uri("/nonexistent")
765 .version(Version::HTTP_11)
766 .headers(HeaderMap::new())
767 .body(Bytes::new())
768 .build()
769 .unwrap();
770
771 let response = handler.handle_request(request).await;
772 let resp = response.unwrap();
773 assert_eq!(resp.status, StatusCode::NOT_FOUND);
774 }
775
776 #[tokio::test]
777 async fn test_handler_multiple_routes() {
778 let mut router = DefaultRouter::new();
780
781 let hello_handler = Arc::new(TestHandler {
782 response_body: "Hello".to_string(),
783 });
784 let mut hello_route = path("/hello", hello_handler);
785 hello_route.name = Some("hello".to_string());
786 router.add_route(hello_route);
787
788 let world_handler = Arc::new(TestHandler {
789 response_body: "World".to_string(),
790 });
791 let mut world_route = path("/world", world_handler);
792 world_route.name = Some("world".to_string());
793 router.add_route(world_route);
794
795 let handler = BaseHandler::with_router(Arc::new(router));
796
797 let request = Request::builder()
799 .method(Method::GET)
800 .uri("/hello")
801 .version(Version::HTTP_11)
802 .headers(HeaderMap::new())
803 .body(Bytes::new())
804 .build()
805 .unwrap();
806 let response = handler.handle_request(request).await.unwrap();
807 assert_eq!(response.status, StatusCode::OK);
808 assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "Hello");
809
810 let request = Request::builder()
812 .method(Method::GET)
813 .uri("/world")
814 .version(Version::HTTP_11)
815 .headers(HeaderMap::new())
816 .body(Bytes::new())
817 .build()
818 .unwrap();
819 let response = handler.handle_request(request).await.unwrap();
820 assert_eq!(response.status, StatusCode::OK);
821 assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "World");
822 }
823}