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