Skip to main content

reinhardt_http/
middleware.rs

1//! Middleware and handler traits for HTTP request processing.
2//!
3//! This module provides the core abstractions for handling HTTP requests
4//! and composing middleware chains.
5//!
6//! ## Handler
7//!
8//! The `Handler` trait is the core abstraction for processing requests:
9//!
10//! ```rust
11//! use reinhardt_http::{Handler, Request, Response};
12//! use async_trait::async_trait;
13//!
14//! struct MyHandler;
15//!
16//! #[async_trait]
17//! impl Handler for MyHandler {
18//!     async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
19//!         Ok(Response::ok().with_body("Hello!"))
20//!     }
21//! }
22//! ```
23//!
24//! ## Middleware
25//!
26//! Middleware wraps handlers to add cross-cutting concerns:
27//!
28//! ```rust
29//! use reinhardt_http::{Handler, Middleware, Request, Response};
30//! use async_trait::async_trait;
31//! use std::sync::Arc;
32//!
33//! struct LoggingMiddleware;
34//!
35//! #[async_trait]
36//! impl Middleware for LoggingMiddleware {
37//!     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
38//!         println!("Request: {} {}", request.method, request.uri);
39//!         next.handle(request).await
40//!     }
41//! }
42//! ```
43
44use async_trait::async_trait;
45use reinhardt_core::exception::{Error, Result};
46use std::any::{Any, TypeId};
47use std::sync::Arc;
48
49use crate::exception::ExceptionHandler;
50use crate::{Request, Response};
51
52/// Type-erased DI singleton registration entry contributed by a middleware.
53///
54/// Pairs the concrete `TypeId` of `T` with an `Arc<dyn Any + Send + Sync>` that
55/// can be inserted directly into a DI singleton scope keyed by that `TypeId`.
56/// This indirection lets `reinhardt-http` expose a DI hook on the `Middleware`
57/// trait without taking a dependency on `reinhardt-di` (which would create a
58/// circular crate dependency).
59pub type MiddlewareDiRegistration = (TypeId, Arc<dyn Any + Send + Sync>);
60
61/// Handler trait for processing requests.
62///
63/// This is the core abstraction - all request handlers implement this trait.
64/// Handlers receive a request and produce a response or an error.
65#[async_trait]
66pub trait Handler: Send + Sync {
67	/// Handles an HTTP request and produces a response.
68	///
69	/// # Errors
70	///
71	/// Returns an error if the request cannot be processed.
72	async fn handle(&self, request: Request) -> Result<Response>;
73}
74
75/// Blanket implementation for `Arc<T>` where T: Handler.
76///
77/// This allows `Arc<dyn Handler>` to be used as a Handler,
78/// enabling shared ownership of handlers across threads.
79#[async_trait]
80impl<T: Handler + ?Sized> Handler for Arc<T> {
81	async fn handle(&self, request: Request) -> Result<Response> {
82		(**self).handle(request).await
83	}
84}
85
86/// Middleware trait for request/response processing.
87///
88/// Uses composition pattern instead of inheritance.
89/// Middleware can modify requests before passing to the next handler,
90/// or modify responses after the handler processes the request.
91#[async_trait]
92pub trait Middleware: Send + Sync {
93	/// Processes a request through this middleware.
94	///
95	/// # Arguments
96	///
97	/// * `request` - The incoming HTTP request
98	/// * `next` - The next handler in the chain to call
99	///
100	/// # Errors
101	///
102	/// Returns an error if the middleware or next handler fails.
103	async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response>;
104
105	/// Determines whether this middleware should be executed for the given request.
106	///
107	/// This method enables conditional execution of middleware, allowing the middleware
108	/// chain to skip unnecessary middleware based on request properties.
109	///
110	/// # Performance Benefits
111	///
112	/// By implementing this method, middleware chains can achieve O(k) complexity
113	/// instead of O(n), where k is the number of middleware that should run,
114	/// and k <= n (total middleware count).
115	///
116	/// # Common Use Cases
117	///
118	/// - Skip authentication middleware for public endpoints
119	/// - Skip compression middleware for already compressed responses
120	/// - Skip CORS middleware for same-origin requests
121	/// - Skip rate limiting for internal/admin requests
122	///
123	/// # Default Implementation
124	///
125	/// By default, returns `true` (always execute), maintaining backward compatibility.
126	fn should_continue(&self, _request: &Request) -> bool {
127		true
128	}
129
130	/// Returns DI singleton registrations contributed by this middleware.
131	///
132	/// Each entry is a `(TypeId, Arc<dyn Any + Send + Sync>)` pair representing
133	/// a singleton that the middleware owns and wants to expose to handlers
134	/// resolved via `#[inject]`. The default implementation returns an empty
135	/// vector, preserving backward compatibility for middleware that does not
136	/// own any DI-visible state.
137	///
138	/// Routers such as `ServerRouter` / `UnifiedRouter` call this method when
139	/// the middleware is registered via `with_middleware()` and merge the
140	/// resulting list into the server's DI singleton scope. This lets a
141	/// middleware (for example `SessionMiddleware`) automatically register the
142	/// `Arc<T>` it constructs in its constructor, so callers no longer have to
143	/// thread a parallel `with_di_registrations(...)` call alongside every
144	/// `with_middleware(...)`.
145	///
146	/// # Example
147	///
148	/// ```rust,ignore
149	/// use std::any::TypeId;
150	/// use std::sync::Arc;
151	/// use reinhardt_http::{Middleware, MiddlewareDiRegistration};
152	///
153	/// struct MyStore;
154	/// struct MyMiddleware { store: Arc<MyStore> }
155	///
156	/// impl Middleware for MyMiddleware {
157	///     // ... process / should_continue ...
158	///     fn di_registrations(&self) -> Vec<MiddlewareDiRegistration> {
159	///         vec![(TypeId::of::<MyStore>(), Arc::clone(&self.store) as _)]
160	///     }
161	/// }
162	/// ```
163	fn di_registrations(&self) -> Vec<MiddlewareDiRegistration> {
164		Vec::new()
165	}
166}
167
168/// Middleware chain - composes multiple middleware into a single handler.
169///
170/// The chain processes requests through middleware in the order they were added,
171/// with optimizations for conditional execution and early termination.
172pub struct MiddlewareChain {
173	middlewares: Vec<Arc<dyn Middleware>>,
174	handler: Arc<dyn Handler>,
175	/// Applied instead of the default `Response::from` conversion when a request
176	/// fails anywhere in this chain, including inside middleware.
177	exception_handler: Option<Arc<dyn ExceptionHandler>>,
178}
179
180impl MiddlewareChain {
181	/// Creates a new middleware chain with the given handler.
182	///
183	/// # Examples
184	///
185	/// ```rust
186	/// use reinhardt_http::{MiddlewareChain, Handler, Request, Response};
187	/// use std::sync::Arc;
188	///
189	/// struct MyHandler;
190	///
191	/// #[async_trait::async_trait]
192	/// impl Handler for MyHandler {
193	///     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
194	///         Ok(Response::ok())
195	///     }
196	/// }
197	///
198	/// let handler = Arc::new(MyHandler);
199	/// let chain = MiddlewareChain::new(handler);
200	/// ```
201	pub fn new(handler: Arc<dyn Handler>) -> Self {
202		Self {
203			middlewares: Vec::new(),
204			handler,
205			exception_handler: None,
206		}
207	}
208
209	/// Creates a middleware chain from an existing middleware stack.
210	pub fn with_middlewares(
211		handler: Arc<dyn Handler>,
212		middlewares: Vec<Arc<dyn Middleware>>,
213	) -> Self {
214		Self {
215			middlewares,
216			handler,
217			exception_handler: None,
218		}
219	}
220
221	/// Installs an exception handler for every failure in this chain.
222	///
223	/// Without one, `Err` values are converted by `impl From<Error> for Response`,
224	/// which omits internal details and returns a JSON `SafeErrorResponse`. The
225	/// installed handler replaces that conversion for errors raised by the base
226	/// handler and for errors raised by middleware in this chain.
227	///
228	/// # Examples
229	///
230	/// ```rust
231	/// use async_trait::async_trait;
232	/// use reinhardt_http::{
233	///     Error, ExceptionHandler, Handler, MiddlewareChain, Request, Response,
234	/// };
235	/// use std::sync::Arc;
236	///
237	/// # struct MyHandler;
238	/// # #[async_trait]
239	/// # impl Handler for MyHandler {
240	/// #     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
241	/// #         Ok(Response::ok())
242	/// #     }
243	/// # }
244	/// struct TeapotErrors;
245	///
246	/// #[async_trait]
247	/// impl ExceptionHandler for TeapotErrors {
248	///     async fn handle_exception(&self, _request: &Request, _error: Error) -> Response {
249	///         Response::new(hyper::StatusCode::IM_A_TEAPOT)
250	///     }
251	/// }
252	///
253	/// let chain = MiddlewareChain::new(Arc::new(MyHandler))
254	///     .with_exception_handler(Arc::new(TeapotErrors));
255	/// ```
256	pub fn with_exception_handler(mut self, exception_handler: Arc<dyn ExceptionHandler>) -> Self {
257		self.exception_handler = Some(exception_handler);
258		self
259	}
260
261	/// Adds a middleware to the chain using builder pattern.
262	///
263	/// # Examples
264	///
265	/// ```rust
266	/// use reinhardt_http::{MiddlewareChain, Handler, Middleware, Request, Response};
267	/// use std::sync::Arc;
268	///
269	/// # struct MyHandler;
270	/// # struct MyMiddleware;
271	/// # #[async_trait::async_trait]
272	/// # impl Handler for MyHandler {
273	/// #     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
274	/// #         Ok(Response::ok())
275	/// #     }
276	/// # }
277	/// # #[async_trait::async_trait]
278	/// # impl Middleware for MyMiddleware {
279	/// #     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
280	/// #         next.handle(request).await
281	/// #     }
282	/// # }
283	/// let handler = Arc::new(MyHandler);
284	/// let middleware = Arc::new(MyMiddleware);
285	/// let chain = MiddlewareChain::new(handler)
286	///     .with_middleware(middleware);
287	/// ```
288	pub fn with_middleware(mut self, middleware: Arc<dyn Middleware>) -> Self {
289		self.middlewares.push(middleware);
290		self
291	}
292
293	/// Adds a middleware to the chain.
294	///
295	/// # Examples
296	///
297	/// ```rust
298	/// use reinhardt_http::{MiddlewareChain, Handler, Middleware, Request, Response};
299	/// use std::sync::Arc;
300	///
301	/// # struct MyHandler;
302	/// # struct MyMiddleware;
303	/// # #[async_trait::async_trait]
304	/// # impl Handler for MyHandler {
305	/// #     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
306	/// #         Ok(Response::ok())
307	/// #     }
308	/// # }
309	/// # #[async_trait::async_trait]
310	/// # impl Middleware for MyMiddleware {
311	/// #     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
312	/// #         next.handle(request).await
313	/// #     }
314	/// # }
315	/// let handler = Arc::new(MyHandler);
316	/// let middleware = Arc::new(MyMiddleware);
317	/// let mut chain = MiddlewareChain::new(handler);
318	/// chain.add_middleware(middleware);
319	/// ```
320	pub fn add_middleware(&mut self, middleware: Arc<dyn Middleware>) {
321		self.middlewares.push(middleware);
322	}
323}
324
325#[async_trait]
326impl Handler for MiddlewareChain {
327	async fn handle(&self, mut request: Request) -> Result<Response> {
328		let exception_handler = self
329			.exception_handler
330			.clone()
331			.or_else(|| request.extensions.get::<Arc<dyn ExceptionHandler>>());
332		if let Some(handler) = &exception_handler {
333			request.install_exception_handler(Arc::clone(handler));
334		}
335		// A chain with no middleware neither converts nor swallows errors, so the
336		// installed handler is applied here to keep `with_exception_handler`
337		// meaningful for a bare chain. The error still propagates as `Err` when no
338		// handler is installed, which is the behaviour callers rely on today.
339		if self.middlewares.is_empty() {
340			let Some(exception_handler) = exception_handler.as_ref() else {
341				return self.handler.handle(request).await;
342			};
343			let mut context = request.clone_for_di();
344			return match self.handler.handle(request).await {
345				Ok(response) => Ok(response),
346				Err(e) => {
347					context.sync_path_params_from_shared_state();
348					context.extensions.insert(crate::ExceptionHandlerInvoked);
349					Ok(exception_handler.handle_exception(&context, e).await)
350				}
351			};
352		}
353
354		if self.middlewares.len() == 1 {
355			let middleware = &self.middlewares[0];
356			if !middleware.should_continue(&request) {
357				let mut context = capture_exception_context(exception_handler.as_ref(), &request);
358				return match self.handler.handle(request).await {
359					Ok(response) => Ok(response),
360					Err(e) => {
361						refresh_exception_context(&mut context);
362						Ok(convert_error(exception_handler.as_ref(), context.as_ref(), e).await)
363					}
364				};
365			}
366
367			let mut context = capture_exception_context(exception_handler.as_ref(), &request);
368			let next: Arc<dyn Handler> = Arc::new(ErrorToResponseHandler {
369				inner: self.handler.clone(),
370				exception_handler: exception_handler.clone(),
371			});
372			let response = match middleware.process(request, next).await {
373				Ok(response) => response,
374				Err(e) => {
375					refresh_exception_context(&mut context);
376					convert_error(exception_handler.as_ref(), context.as_ref(), e).await
377				}
378			};
379			return Ok(response);
380		}
381
382		// Build nested handler chain using composition with optimizations:
383		// 1. Conditional execution (skip middleware based on should_continue)
384		// 2. Short-circuiting (early return if response.should_stop_chain() is true)
385		//
386		// Performance improvements:
387		// - Condition check: O(1) per middleware
388		// - Skip unnecessary middleware: achieves O(k) where k <= n
389		// - Early return: stops processing on first stop_chain=true response
390		// Wrap the base handler to convert errors to responses, ensuring
391		// all middleware post-processing runs even for error responses.
392		let mut current_handler: Arc<dyn Handler> = Arc::new(ErrorToResponseHandler {
393			inner: self.handler.clone(),
394			exception_handler: exception_handler.clone(),
395		});
396
397		for middleware in self
398			.middlewares
399			.iter()
400			.rev()
401			.filter(|mw| mw.should_continue(&request))
402		{
403			let mw = middleware.clone();
404			let handler = current_handler.clone();
405
406			current_handler = Arc::new(ConditionalComposedHandler {
407				middleware: mw,
408				next: handler,
409				exception_handler: exception_handler.clone(),
410			});
411		}
412
413		current_handler.handle(request).await
414	}
415}
416
417/// Middleware wrapper that excludes specific URL paths from execution.
418///
419/// When a request matches an excluded path, the middleware is skipped
420/// and the request passes directly to the next handler in the chain.
421///
422/// Path matching follows Django URL conventions:
423/// - Paths ending with `/` are treated as **prefix matches**
424///   (e.g., `"/api/auth/"` excludes `"/api/auth/login"`, `"/api/auth/register"`)
425/// - Paths without trailing `/` require an **exact match**
426///   (e.g., `"/health"` excludes only `"/health"`, not `"/health/check"`)
427///
428/// This struct is typically not used directly. Instead, use the
429/// `exclude` methods on the `ServerRouter` or `UnifiedRouter` types
430/// from the `reinhardt_urls::routers` module for declarative
431/// route exclusion at the router level.
432///
433/// # Examples
434///
435/// ```rust
436/// use reinhardt_http::middleware::ExcludeMiddleware;
437/// use reinhardt_http::{Middleware, Request};
438/// use std::sync::Arc;
439///
440/// # struct MyMiddleware;
441/// # #[async_trait::async_trait]
442/// # impl Middleware for MyMiddleware {
443/// #     async fn process(
444/// #         &self,
445/// #         request: Request,
446/// #         next: Arc<dyn reinhardt_http::Handler>,
447/// #     ) -> reinhardt_core::exception::Result<reinhardt_http::Response> {
448/// #         next.handle(request).await
449/// #     }
450/// # }
451/// let inner: Arc<dyn Middleware> = Arc::new(MyMiddleware);
452/// let excluded = ExcludeMiddleware::new(inner)
453///     .add_exclusion("/api/auth/")   // prefix match
454///     .add_exclusion("/health");     // exact match
455/// ```
456pub struct ExcludeMiddleware {
457	inner: Arc<dyn Middleware>,
458	exclusions: Vec<String>,
459}
460
461impl ExcludeMiddleware {
462	/// Creates a new `ExcludeMiddleware` wrapping the given middleware.
463	pub fn new(inner: Arc<dyn Middleware>) -> Self {
464		Self {
465			inner,
466			exclusions: Vec::new(),
467		}
468	}
469
470	/// Adds an exclusion pattern (builder pattern, consumes self).
471	///
472	/// Paths ending with `/` are prefix matches; others are exact matches.
473	pub fn add_exclusion(mut self, pattern: &str) -> Self {
474		self.exclusions.push(pattern.to_string());
475		self
476	}
477
478	/// Adds an exclusion pattern (mutable reference).
479	///
480	/// Paths ending with `/` are prefix matches; others are exact matches.
481	pub fn add_exclusion_mut(&mut self, pattern: &str) {
482		self.exclusions.push(pattern.to_string());
483	}
484
485	/// Checks whether the given path matches any exclusion pattern.
486	fn is_excluded(&self, path: &str) -> bool {
487		self.exclusions.iter().any(|pattern| {
488			if pattern.ends_with('/') {
489				// Prefix match: excluded if path starts with the pattern
490				path.starts_with(pattern.as_str())
491			} else {
492				// Exact match: excluded only if path equals the pattern
493				path == pattern
494			}
495		})
496	}
497}
498
499#[async_trait]
500impl Middleware for ExcludeMiddleware {
501	async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
502		self.inner.process(request, next).await
503	}
504
505	fn should_continue(&self, request: &Request) -> bool {
506		if self.is_excluded(request.uri.path()) {
507			return false;
508		}
509		self.inner.should_continue(request)
510	}
511}
512
513/// Captures the request context an installed exception handler needs.
514///
515/// Returns `None` when no handler is installed, so the default error path pays
516/// nothing. The context is produced by `Request::clone_for_di`, which copies
517/// method, URI, version, headers, path parameters and query parameters and
518/// shares the extensions store through an internal `Arc`.
519fn capture_exception_context(
520	exception_handler: Option<&Arc<dyn ExceptionHandler>>,
521	request: &Request,
522) -> Option<Request> {
523	// Bail out before cloning when no handler is installed.
524	exception_handler?;
525	Some(request.clone_for_di())
526}
527
528fn refresh_exception_context(context: &mut Option<Request>) {
529	if let Some(context) = context.as_mut() {
530		context.sync_path_params_from_shared_state();
531	}
532}
533
534/// Converts `error` into a response with the installed handler when both the
535/// handler and a captured context are present, and with the default
536/// `impl From<Error> for Response` otherwise.
537async fn convert_error(
538	exception_handler: Option<&Arc<dyn ExceptionHandler>>,
539	context: Option<&Request>,
540	error: Error,
541) -> Response {
542	match (exception_handler, context) {
543		(Some(handler), Some(context)) => {
544			context.extensions.insert(crate::ExceptionHandlerInvoked);
545			handler.handle_exception(context, error).await
546		}
547		_ => Response::from(error),
548	}
549}
550
551/// Internal handler wrapper that converts errors to HTTP responses.
552///
553/// Wraps the base handler so that middleware always receives `Ok(Response)`
554/// from `next.handle()`, even when the handler returns an error. This ensures
555/// middleware post-processing (e.g., adding security headers) runs for all
556/// responses, matching Django's `process_response` semantics.
557struct ErrorToResponseHandler {
558	inner: Arc<dyn Handler>,
559	exception_handler: Option<Arc<dyn ExceptionHandler>>,
560}
561
562#[async_trait]
563impl Handler for ErrorToResponseHandler {
564	async fn handle(&self, request: Request) -> Result<Response> {
565		let mut context = capture_exception_context(self.exception_handler.as_ref(), &request);
566		match self.inner.handle(request).await {
567			Ok(response) => Ok(response),
568			Err(e) => {
569				if let Some(context) = context.as_mut() {
570					context.sync_path_params_from_shared_state();
571				}
572				Ok(convert_error(self.exception_handler.as_ref(), context.as_ref(), e).await)
573			}
574		}
575	}
576}
577
578/// Internal handler that composes a single middleware with the next handler.
579///
580/// Converts middleware errors to HTTP responses so that outer middleware
581/// post-processing (e.g., adding security headers) always runs.
582struct ConditionalComposedHandler {
583	middleware: Arc<dyn Middleware>,
584	next: Arc<dyn Handler>,
585	exception_handler: Option<Arc<dyn ExceptionHandler>>,
586}
587
588#[async_trait]
589impl Handler for ConditionalComposedHandler {
590	async fn handle(&self, request: Request) -> Result<Response> {
591		// Process the request through this middleware.
592		// Convert errors to responses so that outer middleware post-processing
593		// (e.g., security headers) always runs — matching Django's process_response
594		// semantics where the response hook executes for both success and error cases.
595		let mut context = capture_exception_context(self.exception_handler.as_ref(), &request);
596		let response = match self.middleware.process(request, self.next.clone()).await {
597			Ok(response) => response,
598			Err(e) => {
599				refresh_exception_context(&mut context);
600				convert_error(self.exception_handler.as_ref(), context.as_ref(), e).await
601			}
602		};
603
604		Ok(response)
605	}
606}
607
608#[cfg(test)]
609mod tests {
610	use super::*;
611	use bytes::Bytes;
612	use hyper::{HeaderMap, Method, Version};
613	use rstest::rstest;
614
615	// Mock handler for testing
616	struct MockHandler {
617		response_body: String,
618	}
619
620	#[async_trait]
621	impl Handler for MockHandler {
622		async fn handle(&self, _request: Request) -> Result<Response> {
623			Ok(Response::ok().with_body(self.response_body.clone()))
624		}
625	}
626
627	// Mock middleware for testing
628	struct MockMiddleware {
629		prefix: String,
630	}
631
632	#[async_trait]
633	impl Middleware for MockMiddleware {
634		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
635			// Call the next handler
636			let response = next.handle(request).await?;
637
638			// Modify the response
639			let current_body = String::from_utf8(response.body.to_vec()).unwrap_or_default();
640			let new_body = format!("{}{}", self.prefix, current_body);
641
642			Ok(Response::ok().with_body(new_body))
643		}
644	}
645
646	fn create_test_request() -> Request {
647		Request::builder()
648			.method(Method::GET)
649			.uri("/")
650			.version(Version::HTTP_11)
651			.headers(HeaderMap::new())
652			.body(Bytes::new())
653			.build()
654			.unwrap()
655	}
656
657	#[tokio::test]
658	async fn test_handler_basic() {
659		let handler = MockHandler {
660			response_body: "Hello".to_string(),
661		};
662
663		let request = create_test_request();
664		let response = handler.handle(request).await.unwrap();
665
666		let body = String::from_utf8(response.body.to_vec()).unwrap();
667		assert_eq!(body, "Hello");
668	}
669
670	#[tokio::test]
671	async fn test_middleware_basic() {
672		let handler = Arc::new(MockHandler {
673			response_body: "World".to_string(),
674		});
675
676		let middleware = MockMiddleware {
677			prefix: "Hello, ".to_string(),
678		};
679
680		let request = create_test_request();
681		let response = middleware.process(request, handler).await.unwrap();
682
683		let body = String::from_utf8(response.body.to_vec()).unwrap();
684		assert_eq!(body, "Hello, World");
685	}
686
687	#[tokio::test]
688	async fn test_middleware_chain_empty() {
689		let handler = Arc::new(MockHandler {
690			response_body: "Test".to_string(),
691		});
692
693		let chain = MiddlewareChain::new(handler);
694
695		let request = create_test_request();
696		let response = chain.handle(request).await.unwrap();
697
698		let body = String::from_utf8(response.body.to_vec()).unwrap();
699		assert_eq!(body, "Test");
700	}
701
702	#[tokio::test]
703	async fn test_middleware_chain_single() {
704		let handler = Arc::new(MockHandler {
705			response_body: "Handler".to_string(),
706		});
707
708		let middleware1 = Arc::new(MockMiddleware {
709			prefix: "MW1:".to_string(),
710		});
711
712		let chain = MiddlewareChain::new(handler).with_middleware(middleware1);
713
714		let request = create_test_request();
715		let response = chain.handle(request).await.unwrap();
716
717		let body = String::from_utf8(response.body.to_vec()).unwrap();
718		assert_eq!(body, "MW1:Handler");
719	}
720
721	#[tokio::test]
722	async fn test_middleware_chain_multiple() {
723		let handler = Arc::new(MockHandler {
724			response_body: "Data".to_string(),
725		});
726
727		let middleware1 = Arc::new(MockMiddleware {
728			prefix: "M1:".to_string(),
729		});
730
731		let middleware2 = Arc::new(MockMiddleware {
732			prefix: "M2:".to_string(),
733		});
734
735		let chain = MiddlewareChain::new(handler)
736			.with_middleware(middleware1)
737			.with_middleware(middleware2);
738
739		let request = create_test_request();
740		let response = chain.handle(request).await.unwrap();
741
742		let body = String::from_utf8(response.body.to_vec()).unwrap();
743		// Middleware are applied in the order they were added
744		assert_eq!(body, "M1:M2:Data");
745	}
746
747	#[tokio::test]
748	async fn test_middleware_chain_add_middleware() {
749		let handler = Arc::new(MockHandler {
750			response_body: "Result".to_string(),
751		});
752
753		let middleware = Arc::new(MockMiddleware {
754			prefix: "Prefix:".to_string(),
755		});
756
757		let mut chain = MiddlewareChain::new(handler);
758		chain.add_middleware(middleware);
759
760		let request = create_test_request();
761		let response = chain.handle(request).await.unwrap();
762
763		let body = String::from_utf8(response.body.to_vec()).unwrap();
764		assert_eq!(body, "Prefix:Result");
765	}
766
767	// Conditional middleware that only runs for /api/* paths
768	struct ConditionalMiddleware {
769		prefix: String,
770	}
771
772	#[async_trait]
773	impl Middleware for ConditionalMiddleware {
774		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
775			let response = next.handle(request).await?;
776			let current_body = String::from_utf8(response.body.to_vec()).unwrap_or_default();
777			let new_body = format!("{}{}", self.prefix, current_body);
778			Ok(Response::ok().with_body(new_body))
779		}
780
781		fn should_continue(&self, request: &Request) -> bool {
782			request.uri.path().starts_with("/api/")
783		}
784	}
785
786	#[tokio::test]
787	async fn test_middleware_conditional_skip() {
788		let handler = Arc::new(MockHandler {
789			response_body: "Response".to_string(),
790		});
791
792		let conditional_mw = Arc::new(ConditionalMiddleware {
793			prefix: "API:".to_string(),
794		});
795
796		let chain = MiddlewareChain::new(handler).with_middleware(conditional_mw);
797
798		// Test with /api/ path - middleware should run
799		let api_request = Request::builder()
800			.method(Method::GET)
801			.uri("/api/users")
802			.version(Version::HTTP_11)
803			.headers(HeaderMap::new())
804			.body(Bytes::new())
805			.build()
806			.unwrap();
807		let response = chain.handle(api_request).await.unwrap();
808		let body = String::from_utf8(response.body.to_vec()).unwrap();
809		assert_eq!(body, "API:Response");
810
811		// Test with non-/api/ path - middleware should be skipped
812		let non_api_request = Request::builder()
813			.method(Method::GET)
814			.uri("/public")
815			.version(Version::HTTP_11)
816			.headers(HeaderMap::new())
817			.body(Bytes::new())
818			.build()
819			.unwrap();
820		let response = chain.handle(non_api_request).await.unwrap();
821		let body = String::from_utf8(response.body.to_vec()).unwrap();
822		assert_eq!(body, "Response"); // No prefix because middleware was skipped
823	}
824
825	// Middleware that returns early with stop_chain=true
826	struct ShortCircuitMiddleware {
827		should_stop: bool,
828	}
829
830	#[async_trait]
831	impl Middleware for ShortCircuitMiddleware {
832		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
833			if self.should_stop {
834				// Return early without calling next
835				return Ok(Response::unauthorized()
836					.with_body("Auth required")
837					.with_stop_chain(true));
838			}
839			next.handle(request).await
840		}
841	}
842
843	#[tokio::test]
844	async fn test_middleware_short_circuit() {
845		let handler = Arc::new(MockHandler {
846			response_body: "Handler Response".to_string(),
847		});
848
849		let short_circuit_mw = Arc::new(ShortCircuitMiddleware { should_stop: true });
850		let normal_mw = Arc::new(MockMiddleware {
851			prefix: "Normal:".to_string(),
852		});
853
854		let chain = MiddlewareChain::new(handler)
855			.with_middleware(short_circuit_mw)
856			.with_middleware(normal_mw);
857
858		let request = create_test_request();
859		let response = chain.handle(request).await.unwrap();
860
861		// Should get unauthorized response, not the handler response
862		assert_eq!(response.status, hyper::StatusCode::UNAUTHORIZED);
863		let body = String::from_utf8(response.body.to_vec()).unwrap();
864		assert_eq!(body, "Auth required");
865	}
866
867	#[tokio::test]
868	async fn test_middleware_no_short_circuit() {
869		let handler = Arc::new(MockHandler {
870			response_body: "Handler Response".to_string(),
871		});
872
873		let short_circuit_mw = Arc::new(ShortCircuitMiddleware { should_stop: false });
874		let normal_mw = Arc::new(MockMiddleware {
875			prefix: "Normal:".to_string(),
876		});
877
878		let chain = MiddlewareChain::new(handler)
879			.with_middleware(short_circuit_mw)
880			.with_middleware(normal_mw);
881
882		let request = create_test_request();
883		let response = chain.handle(request).await.unwrap();
884
885		// Should pass through to handler and apply normal middleware
886		assert_eq!(response.status, hyper::StatusCode::OK);
887		let body = String::from_utf8(response.body.to_vec()).unwrap();
888		assert_eq!(body, "Normal:Handler Response");
889	}
890
891	#[tokio::test]
892	async fn test_middleware_multiple_conditions() {
893		let handler = Arc::new(MockHandler {
894			response_body: "Base".to_string(),
895		});
896
897		// Only runs for /api/* paths
898		let api_mw = Arc::new(ConditionalMiddleware {
899			prefix: "API:".to_string(),
900		});
901
902		// Always runs
903		let always_mw = Arc::new(MockMiddleware {
904			prefix: "Always:".to_string(),
905		});
906
907		let chain = MiddlewareChain::new(handler)
908			.with_middleware(api_mw)
909			.with_middleware(always_mw);
910
911		// Test with /api/ path - both middleware should run
912		let api_request = Request::builder()
913			.method(Method::GET)
914			.uri("/api/test")
915			.version(Version::HTTP_11)
916			.headers(HeaderMap::new())
917			.body(Bytes::new())
918			.build()
919			.unwrap();
920		let response = chain.handle(api_request).await.unwrap();
921		let body = String::from_utf8(response.body.to_vec()).unwrap();
922		assert_eq!(body, "API:Always:Base");
923
924		// Test with non-/api/ path - only always_mw should run
925		let non_api_request = Request::builder()
926			.method(Method::GET)
927			.uri("/public")
928			.version(Version::HTTP_11)
929			.headers(HeaderMap::new())
930			.body(Bytes::new())
931			.build()
932			.unwrap();
933		let response = chain.handle(non_api_request).await.unwrap();
934		let body = String::from_utf8(response.body.to_vec()).unwrap();
935		assert_eq!(body, "Always:Base"); // Only always_mw prefix
936	}
937
938	#[tokio::test]
939	async fn test_response_should_stop_chain() {
940		let response = Response::ok();
941		assert!(!response.should_stop_chain());
942
943		let stopping_response = Response::unauthorized().with_stop_chain(true);
944		assert!(stopping_response.should_stop_chain());
945	}
946
947	// --- ExcludeMiddleware tests ---
948
949	fn create_request_with_path(path: &str) -> Request {
950		Request::builder()
951			.method(Method::GET)
952			.uri(path)
953			.version(Version::HTTP_11)
954			.headers(HeaderMap::new())
955			.body(Bytes::new())
956			.build()
957			.unwrap()
958	}
959
960	#[rstest::rstest]
961	#[case("/api/auth/login", true)]
962	#[case("/api/auth/register", true)]
963	#[case("/api/auth/", true)]
964	#[case("/api/users", false)]
965	#[case("/public", false)]
966	fn test_exclude_middleware_prefix_match(#[case] path: &str, #[case] should_exclude: bool) {
967		// Arrange
968		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
969			prefix: "MW:".to_string(),
970		});
971		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/api/auth/");
972
973		// Act
974		let request = create_request_with_path(path);
975		let result = exclude_mw.should_continue(&request);
976
977		// Assert
978		assert_eq!(result, !should_exclude);
979	}
980
981	#[rstest::rstest]
982	#[case("/health", true)]
983	#[case("/health/check", false)]
984	#[case("/healthz", false)]
985	#[case("/api/health", false)]
986	fn test_exclude_middleware_exact_match(#[case] path: &str, #[case] should_exclude: bool) {
987		// Arrange
988		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
989			prefix: "MW:".to_string(),
990		});
991		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/health");
992
993		// Act
994		let request = create_request_with_path(path);
995		let result = exclude_mw.should_continue(&request);
996
997		// Assert
998		assert_eq!(result, !should_exclude);
999	}
1000
1001	#[rstest::rstest]
1002	fn test_exclude_middleware_no_match_passes_through() {
1003		// Arrange
1004		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
1005			prefix: "MW:".to_string(),
1006		});
1007		let exclude_mw = ExcludeMiddleware::new(inner)
1008			.add_exclusion("/api/auth/")
1009			.add_exclusion("/health");
1010
1011		// Act
1012		let request = create_request_with_path("/api/users");
1013		let result = exclude_mw.should_continue(&request);
1014
1015		// Assert
1016		assert!(result);
1017	}
1018
1019	#[rstest::rstest]
1020	#[tokio::test]
1021	async fn test_exclude_middleware_delegates_process() {
1022		// Arrange
1023		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
1024			prefix: "INNER:".to_string(),
1025		});
1026		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/excluded/");
1027
1028		let handler = Arc::new(MockHandler {
1029			response_body: "Response".to_string(),
1030		});
1031
1032		// Act
1033		let request = create_request_with_path("/api/test");
1034		let response = exclude_mw.process(request, handler).await.unwrap();
1035
1036		// Assert
1037		let body = String::from_utf8(response.body.to_vec()).unwrap();
1038		assert_eq!(body, "INNER:Response");
1039	}
1040
1041	#[rstest::rstest]
1042	fn test_exclude_middleware_multiple_exclusions() {
1043		// Arrange
1044		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
1045			prefix: "MW:".to_string(),
1046		});
1047		let mut exclude_mw = ExcludeMiddleware::new(inner);
1048		exclude_mw.add_exclusion_mut("/api/auth/");
1049		exclude_mw.add_exclusion_mut("/admin/");
1050		exclude_mw.add_exclusion_mut("/health");
1051
1052		// Act & Assert
1053		assert!(!exclude_mw.should_continue(&create_request_with_path("/api/auth/login")));
1054		assert!(!exclude_mw.should_continue(&create_request_with_path("/admin/dashboard")));
1055		assert!(!exclude_mw.should_continue(&create_request_with_path("/health")));
1056		assert!(exclude_mw.should_continue(&create_request_with_path("/api/users")));
1057	}
1058
1059	#[rstest::rstest]
1060	fn test_exclude_middleware_respects_inner_should_continue() {
1061		// Arrange - inner middleware that rejects non-/api/ paths
1062		let inner: Arc<dyn Middleware> = Arc::new(ConditionalMiddleware {
1063			prefix: "API:".to_string(),
1064		});
1065		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/api/auth/");
1066
1067		// Act & Assert
1068		// Excluded path -> false (excluded by wrapper)
1069		assert!(!exclude_mw.should_continue(&create_request_with_path("/api/auth/login")));
1070		// Non-excluded, but inner rejects non-/api/ -> false (inner's should_continue)
1071		assert!(!exclude_mw.should_continue(&create_request_with_path("/public")));
1072		// Non-excluded, inner accepts /api/ -> true
1073		assert!(exclude_mw.should_continue(&create_request_with_path("/api/users")));
1074	}
1075
1076	// ========================================================================
1077	// Error-to-response conversion tests (issue #3230)
1078	// ========================================================================
1079
1080	/// Handler that always returns an error.
1081	struct NotFoundHandler;
1082
1083	#[async_trait]
1084	impl Handler for NotFoundHandler {
1085		async fn handle(&self, _request: Request) -> Result<Response> {
1086			Err(reinhardt_core::exception::Error::NotFound(
1087				"not found".into(),
1088			))
1089		}
1090	}
1091
1092	struct UnauthorizedHandler;
1093
1094	#[async_trait]
1095	impl Handler for UnauthorizedHandler {
1096		async fn handle(&self, _request: Request) -> Result<Response> {
1097			Err(reinhardt_core::exception::Error::Authentication(
1098				"unauthorized".into(),
1099			))
1100		}
1101	}
1102
1103	/// Middleware that adds a custom header to the response after calling next.
1104	struct HeaderAddingMiddleware {
1105		header_name: &'static str,
1106		header_value: &'static str,
1107	}
1108
1109	#[async_trait]
1110	impl Middleware for HeaderAddingMiddleware {
1111		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
1112			let response = next.handle(request).await?;
1113			Ok(response.with_header(self.header_name, self.header_value))
1114		}
1115	}
1116
1117	/// Middleware that always returns an error (simulates CSRF rejection).
1118	struct RejectingMiddleware;
1119
1120	#[async_trait]
1121	impl Middleware for RejectingMiddleware {
1122		async fn process(&self, _request: Request, _next: Arc<dyn Handler>) -> Result<Response> {
1123			Err(reinhardt_core::exception::Error::Authorization(
1124				"CSRF check failed".into(),
1125			))
1126		}
1127	}
1128
1129	#[rstest::rstest]
1130	#[tokio::test]
1131	async fn test_chain_post_processing_runs_on_handler_error() {
1132		// Arrange: handler returns 404 error, outer middleware adds header
1133		let handler: Arc<dyn Handler> = Arc::new(NotFoundHandler);
1134		let mut chain = MiddlewareChain::new(handler);
1135		chain.add_middleware(Arc::new(HeaderAddingMiddleware {
1136			header_name: "X-Custom-Security",
1137			header_value: "applied",
1138		}));
1139
1140		// Act
1141		let request = create_test_request();
1142		let response = chain.handle(request).await.unwrap();
1143
1144		// Assert: error converted to 404 response AND header is present
1145		assert_eq!(response.status, hyper::StatusCode::NOT_FOUND);
1146		assert_eq!(
1147			response
1148				.headers
1149				.get("X-Custom-Security")
1150				.map(|v| v.to_str().unwrap()),
1151			Some("applied")
1152		);
1153	}
1154
1155	#[rstest::rstest]
1156	#[tokio::test]
1157	async fn test_chain_post_processing_runs_on_middleware_error() {
1158		// Arrange: outer middleware adds header, inner middleware rejects.
1159		// First add = outermost in this framework's chain ordering.
1160		let handler = Arc::new(MockHandler {
1161			response_body: "OK".into(),
1162		});
1163		let mut chain = MiddlewareChain::new(handler);
1164		// Outer middleware adds a security header (post-processing)
1165		chain.add_middleware(Arc::new(HeaderAddingMiddleware {
1166			header_name: "X-Frame-Options",
1167			header_value: "DENY",
1168		}));
1169		// Inner middleware rejects the request
1170		chain.add_middleware(Arc::new(RejectingMiddleware));
1171
1172		// Act
1173		let request = create_test_request();
1174		let response = chain.handle(request).await.unwrap();
1175
1176		// Assert: inner middleware error converted to 403, outer middleware header present
1177		assert_eq!(response.status, hyper::StatusCode::FORBIDDEN);
1178		assert_eq!(
1179			response
1180				.headers
1181				.get("X-Frame-Options")
1182				.map(|v| v.to_str().unwrap()),
1183			Some("DENY")
1184		);
1185	}
1186
1187	/// Passthrough middleware that does not modify the response.
1188	struct PassthroughMiddleware;
1189
1190	#[async_trait]
1191	impl Middleware for PassthroughMiddleware {
1192		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
1193			next.handle(request).await
1194		}
1195	}
1196
1197	#[rstest::rstest]
1198	#[tokio::test]
1199	async fn test_chain_error_preserves_correct_status_code() {
1200		// Arrange: handler returns 401 Unauthorized, with at least one middleware
1201		// so that ConditionalComposedHandler is used (empty chain bypasses it)
1202		let handler: Arc<dyn Handler> = Arc::new(UnauthorizedHandler);
1203		let mut chain = MiddlewareChain::new(handler);
1204		chain.add_middleware(Arc::new(PassthroughMiddleware));
1205
1206		// Act
1207		let request = create_test_request();
1208		let response = chain.handle(request).await.unwrap();
1209
1210		// Assert: status code correctly reflects the error
1211		assert_eq!(response.status, hyper::StatusCode::UNAUTHORIZED);
1212	}
1213
1214	// ==========================================================================
1215	// Exception handler support (Issue #6294)
1216	// ==========================================================================
1217
1218	/// Middleware that always fails.
1219	struct FailingMiddleware;
1220
1221	#[async_trait]
1222	impl Middleware for FailingMiddleware {
1223		async fn process(&self, _request: Request, _next: Arc<dyn Handler>) -> Result<Response> {
1224			Err(Error::Http("rejected by middleware".to_string()))
1225		}
1226	}
1227
1228	/// Base handler that always fails.
1229	struct ErroringHandler;
1230
1231	#[async_trait]
1232	impl Handler for ErroringHandler {
1233		async fn handle(&self, _request: Request) -> Result<Response> {
1234			Err(Error::NotFound("no route".to_string()))
1235		}
1236	}
1237
1238	/// Exception handler producing a body identifiable in assertions.
1239	struct TeapotHandler;
1240
1241	#[async_trait]
1242	impl ExceptionHandler for TeapotHandler {
1243		async fn handle_exception(&self, _request: &Request, _error: Error) -> Response {
1244			Response::new(hyper::StatusCode::IM_A_TEAPOT).with_body("teapot")
1245		}
1246	}
1247
1248	#[rstest]
1249	#[tokio::test]
1250	async fn test_chain_exception_handler_converts_middleware_error() {
1251		// Arrange
1252		let chain = MiddlewareChain::new(Arc::new(MockHandler {
1253			response_body: "unused".to_string(),
1254		}))
1255		.with_middleware(Arc::new(FailingMiddleware))
1256		.with_exception_handler(Arc::new(TeapotHandler));
1257
1258		// Act
1259		let response = chain.handle(create_test_request()).await.unwrap();
1260
1261		// Assert
1262		assert_eq!(response.status, hyper::StatusCode::IM_A_TEAPOT);
1263		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "teapot");
1264	}
1265
1266	#[rstest]
1267	#[tokio::test]
1268	async fn test_chain_without_exception_handler_keeps_default_middleware_error() {
1269		// Arrange
1270		let chain = MiddlewareChain::new(Arc::new(MockHandler {
1271			response_body: "unused".to_string(),
1272		}))
1273		.with_middleware(Arc::new(FailingMiddleware));
1274
1275		// Act
1276		let response = chain.handle(create_test_request()).await.unwrap();
1277
1278		// Assert: the default conversion maps Error::Http to 400, not to the handler's status
1279		assert_eq!(response.status, hyper::StatusCode::BAD_REQUEST);
1280	}
1281
1282	#[rstest]
1283	#[tokio::test]
1284	async fn test_chain_exception_handler_converts_base_handler_error() {
1285		// Arrange
1286		let chain = MiddlewareChain::new(Arc::new(ErroringHandler))
1287			.with_middleware(Arc::new(PassthroughMiddleware))
1288			.with_exception_handler(Arc::new(TeapotHandler));
1289
1290		// Act
1291		let response = chain.handle(create_test_request()).await.unwrap();
1292
1293		// Assert
1294		assert_eq!(response.status, hyper::StatusCode::IM_A_TEAPOT);
1295	}
1296
1297	#[rstest]
1298	#[tokio::test]
1299	async fn test_bare_chain_with_exception_handler_converts_base_handler_error() {
1300		// Arrange: no middleware, so the chain takes its early-return path
1301		let chain = MiddlewareChain::new(Arc::new(ErroringHandler))
1302			.with_exception_handler(Arc::new(TeapotHandler));
1303
1304		// Act
1305		let response = chain.handle(create_test_request()).await.unwrap();
1306
1307		// Assert
1308		assert_eq!(response.status, hyper::StatusCode::IM_A_TEAPOT);
1309		assert_eq!(String::from_utf8(response.body.to_vec()).unwrap(), "teapot");
1310	}
1311
1312	#[rstest]
1313	#[tokio::test]
1314	async fn test_bare_chain_without_exception_handler_propagates_error() {
1315		// Arrange
1316		let chain = MiddlewareChain::new(Arc::new(ErroringHandler));
1317
1318		// Act
1319		let result = chain.handle(create_test_request()).await;
1320
1321		// Assert: a chain with no middleware must not start swallowing errors
1322		assert!(result.is_err());
1323	}
1324}