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::sync::Arc;
47
48use crate::{Request, Response};
49
50/// Handler trait for processing requests.
51///
52/// This is the core abstraction - all request handlers implement this trait.
53/// Handlers receive a request and produce a response or an error.
54#[async_trait]
55pub trait Handler: Send + Sync {
56	/// Handles an HTTP request and produces a response.
57	///
58	/// # Errors
59	///
60	/// Returns an error if the request cannot be processed.
61	async fn handle(&self, request: Request) -> Result<Response>;
62}
63
64/// Blanket implementation for `Arc<T>` where T: Handler.
65///
66/// This allows `Arc<dyn Handler>` to be used as a Handler,
67/// enabling shared ownership of handlers across threads.
68#[async_trait]
69impl<T: Handler + ?Sized> Handler for Arc<T> {
70	async fn handle(&self, request: Request) -> Result<Response> {
71		(**self).handle(request).await
72	}
73}
74
75/// Middleware trait for request/response processing.
76///
77/// Uses composition pattern instead of inheritance.
78/// Middleware can modify requests before passing to the next handler,
79/// or modify responses after the handler processes the request.
80#[async_trait]
81pub trait Middleware: Send + Sync {
82	/// Processes a request through this middleware.
83	///
84	/// # Arguments
85	///
86	/// * `request` - The incoming HTTP request
87	/// * `next` - The next handler in the chain to call
88	///
89	/// # Errors
90	///
91	/// Returns an error if the middleware or next handler fails.
92	async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response>;
93
94	/// Determines whether this middleware should be executed for the given request.
95	///
96	/// This method enables conditional execution of middleware, allowing the middleware
97	/// chain to skip unnecessary middleware based on request properties.
98	///
99	/// # Performance Benefits
100	///
101	/// By implementing this method, middleware chains can achieve O(k) complexity
102	/// instead of O(n), where k is the number of middleware that should run,
103	/// and k <= n (total middleware count).
104	///
105	/// # Common Use Cases
106	///
107	/// - Skip authentication middleware for public endpoints
108	/// - Skip compression middleware for already compressed responses
109	/// - Skip CORS middleware for same-origin requests
110	/// - Skip rate limiting for internal/admin requests
111	///
112	/// # Default Implementation
113	///
114	/// By default, returns `true` (always execute), maintaining backward compatibility.
115	fn should_continue(&self, _request: &Request) -> bool {
116		true
117	}
118}
119
120/// Middleware chain - composes multiple middleware into a single handler.
121///
122/// The chain processes requests through middleware in the order they were added,
123/// with optimizations for conditional execution and early termination.
124pub struct MiddlewareChain {
125	middlewares: Vec<Arc<dyn Middleware>>,
126	handler: Arc<dyn Handler>,
127}
128
129impl MiddlewareChain {
130	/// Creates a new middleware chain with the given handler.
131	///
132	/// # Examples
133	///
134	/// ```rust
135	/// use reinhardt_http::{MiddlewareChain, Handler, Request, Response};
136	/// use std::sync::Arc;
137	///
138	/// struct MyHandler;
139	///
140	/// #[async_trait::async_trait]
141	/// impl Handler for MyHandler {
142	///     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
143	///         Ok(Response::ok())
144	///     }
145	/// }
146	///
147	/// let handler = Arc::new(MyHandler);
148	/// let chain = MiddlewareChain::new(handler);
149	/// ```
150	pub fn new(handler: Arc<dyn Handler>) -> Self {
151		Self {
152			middlewares: Vec::new(),
153			handler,
154		}
155	}
156
157	/// Adds a middleware to the chain using builder pattern.
158	///
159	/// # Examples
160	///
161	/// ```rust
162	/// use reinhardt_http::{MiddlewareChain, Handler, Middleware, Request, Response};
163	/// use std::sync::Arc;
164	///
165	/// # struct MyHandler;
166	/// # struct MyMiddleware;
167	/// # #[async_trait::async_trait]
168	/// # impl Handler for MyHandler {
169	/// #     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
170	/// #         Ok(Response::ok())
171	/// #     }
172	/// # }
173	/// # #[async_trait::async_trait]
174	/// # impl Middleware for MyMiddleware {
175	/// #     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
176	/// #         next.handle(request).await
177	/// #     }
178	/// # }
179	/// let handler = Arc::new(MyHandler);
180	/// let middleware = Arc::new(MyMiddleware);
181	/// let chain = MiddlewareChain::new(handler)
182	///     .with_middleware(middleware);
183	/// ```
184	pub fn with_middleware(mut self, middleware: Arc<dyn Middleware>) -> Self {
185		self.middlewares.push(middleware);
186		self
187	}
188
189	/// Adds a middleware to the chain.
190	///
191	/// # Examples
192	///
193	/// ```rust
194	/// use reinhardt_http::{MiddlewareChain, Handler, Middleware, Request, Response};
195	/// use std::sync::Arc;
196	///
197	/// # struct MyHandler;
198	/// # struct MyMiddleware;
199	/// # #[async_trait::async_trait]
200	/// # impl Handler for MyHandler {
201	/// #     async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
202	/// #         Ok(Response::ok())
203	/// #     }
204	/// # }
205	/// # #[async_trait::async_trait]
206	/// # impl Middleware for MyMiddleware {
207	/// #     async fn process(&self, request: Request, next: Arc<dyn Handler>) -> reinhardt_core::exception::Result<Response> {
208	/// #         next.handle(request).await
209	/// #     }
210	/// # }
211	/// let handler = Arc::new(MyHandler);
212	/// let middleware = Arc::new(MyMiddleware);
213	/// let mut chain = MiddlewareChain::new(handler);
214	/// chain.add_middleware(middleware);
215	/// ```
216	pub fn add_middleware(&mut self, middleware: Arc<dyn Middleware>) {
217		self.middlewares.push(middleware);
218	}
219}
220
221#[async_trait]
222impl Handler for MiddlewareChain {
223	async fn handle(&self, request: Request) -> Result<Response> {
224		if self.middlewares.is_empty() {
225			return self.handler.handle(request).await;
226		}
227
228		// Build nested handler chain using composition with optimizations:
229		// 1. Conditional execution (skip middleware based on should_continue)
230		// 2. Short-circuiting (early return if response.should_stop_chain() is true)
231		//
232		// Performance improvements:
233		// - Condition check: O(1) per middleware
234		// - Skip unnecessary middleware: achieves O(k) where k <= n
235		// - Early return: stops processing on first stop_chain=true response
236		let mut current_handler = self.handler.clone();
237
238		// Filter middleware based on should_continue condition
239		// This achieves the O(k) optimization where k is the number of middleware that should run
240		let active_middlewares: Vec<_> = self
241			.middlewares
242			.iter()
243			.rev()
244			.filter(|mw| mw.should_continue(&request))
245			.collect();
246
247		for middleware in active_middlewares {
248			let mw = middleware.clone();
249			let handler = current_handler.clone();
250
251			current_handler = Arc::new(ConditionalComposedHandler {
252				middleware: mw,
253				next: handler,
254			});
255		}
256
257		current_handler.handle(request).await
258	}
259}
260
261/// Middleware wrapper that excludes specific URL paths from execution.
262///
263/// When a request matches an excluded path, the middleware is skipped
264/// and the request passes directly to the next handler in the chain.
265///
266/// Path matching follows Django URL conventions:
267/// - Paths ending with `/` are treated as **prefix matches**
268///   (e.g., `"/api/auth/"` excludes `"/api/auth/login"`, `"/api/auth/register"`)
269/// - Paths without trailing `/` require an **exact match**
270///   (e.g., `"/health"` excludes only `"/health"`, not `"/health/check"`)
271///
272/// This struct is typically not used directly. Instead, use the
273/// `exclude` methods on the `ServerRouter` or `UnifiedRouter` types
274/// from the `reinhardt_urls::routers` module for declarative
275/// route exclusion at the router level.
276///
277/// # Examples
278///
279/// ```rust
280/// use reinhardt_http::middleware::ExcludeMiddleware;
281/// use reinhardt_http::{Middleware, Request};
282/// use std::sync::Arc;
283///
284/// # struct MyMiddleware;
285/// # #[async_trait::async_trait]
286/// # impl Middleware for MyMiddleware {
287/// #     async fn process(
288/// #         &self,
289/// #         request: Request,
290/// #         next: Arc<dyn reinhardt_http::Handler>,
291/// #     ) -> reinhardt_core::exception::Result<reinhardt_http::Response> {
292/// #         next.handle(request).await
293/// #     }
294/// # }
295/// let inner: Arc<dyn Middleware> = Arc::new(MyMiddleware);
296/// let excluded = ExcludeMiddleware::new(inner)
297///     .add_exclusion("/api/auth/")   // prefix match
298///     .add_exclusion("/health");     // exact match
299/// ```
300pub struct ExcludeMiddleware {
301	inner: Arc<dyn Middleware>,
302	exclusions: Vec<String>,
303}
304
305impl ExcludeMiddleware {
306	/// Creates a new `ExcludeMiddleware` wrapping the given middleware.
307	pub fn new(inner: Arc<dyn Middleware>) -> Self {
308		Self {
309			inner,
310			exclusions: Vec::new(),
311		}
312	}
313
314	/// Adds an exclusion pattern (builder pattern, consumes self).
315	///
316	/// Paths ending with `/` are prefix matches; others are exact matches.
317	pub fn add_exclusion(mut self, pattern: &str) -> Self {
318		self.exclusions.push(pattern.to_string());
319		self
320	}
321
322	/// Adds an exclusion pattern (mutable reference).
323	///
324	/// Paths ending with `/` are prefix matches; others are exact matches.
325	pub fn add_exclusion_mut(&mut self, pattern: &str) {
326		self.exclusions.push(pattern.to_string());
327	}
328
329	/// Checks whether the given path matches any exclusion pattern.
330	fn is_excluded(&self, path: &str) -> bool {
331		self.exclusions.iter().any(|pattern| {
332			if pattern.ends_with('/') {
333				// Prefix match: excluded if path starts with the pattern
334				path.starts_with(pattern.as_str())
335			} else {
336				// Exact match: excluded only if path equals the pattern
337				path == pattern
338			}
339		})
340	}
341}
342
343#[async_trait]
344impl Middleware for ExcludeMiddleware {
345	async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
346		self.inner.process(request, next).await
347	}
348
349	fn should_continue(&self, request: &Request) -> bool {
350		if self.is_excluded(request.uri.path()) {
351			return false;
352		}
353		self.inner.should_continue(request)
354	}
355}
356
357/// Optimized internal handler that composes middleware with next handler.
358///
359/// Supports short-circuiting via `response.should_stop_chain()`.
360struct ConditionalComposedHandler {
361	middleware: Arc<dyn Middleware>,
362	next: Arc<dyn Handler>,
363}
364
365#[async_trait]
366impl Handler for ConditionalComposedHandler {
367	async fn handle(&self, request: Request) -> Result<Response> {
368		// Process the request through this middleware
369		let response = self.middleware.process(request, self.next.clone()).await?;
370
371		// Short-circuit: if response indicates chain should stop, return immediately
372		// This prevents further middleware/handlers from executing
373		if response.should_stop_chain() {
374			return Ok(response);
375		}
376
377		Ok(response)
378	}
379}
380
381#[cfg(test)]
382mod tests {
383	use super::*;
384	use bytes::Bytes;
385	use hyper::{HeaderMap, Method, Version};
386
387	// Mock handler for testing
388	struct MockHandler {
389		response_body: String,
390	}
391
392	#[async_trait]
393	impl Handler for MockHandler {
394		async fn handle(&self, _request: Request) -> Result<Response> {
395			Ok(Response::ok().with_body(self.response_body.clone()))
396		}
397	}
398
399	// Mock middleware for testing
400	struct MockMiddleware {
401		prefix: String,
402	}
403
404	#[async_trait]
405	impl Middleware for MockMiddleware {
406		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
407			// Call the next handler
408			let response = next.handle(request).await?;
409
410			// Modify the response
411			let current_body = String::from_utf8(response.body.to_vec()).unwrap_or_default();
412			let new_body = format!("{}{}", self.prefix, current_body);
413
414			Ok(Response::ok().with_body(new_body))
415		}
416	}
417
418	fn create_test_request() -> Request {
419		Request::builder()
420			.method(Method::GET)
421			.uri("/")
422			.version(Version::HTTP_11)
423			.headers(HeaderMap::new())
424			.body(Bytes::new())
425			.build()
426			.unwrap()
427	}
428
429	#[tokio::test]
430	async fn test_handler_basic() {
431		let handler = MockHandler {
432			response_body: "Hello".to_string(),
433		};
434
435		let request = create_test_request();
436		let response = handler.handle(request).await.unwrap();
437
438		let body = String::from_utf8(response.body.to_vec()).unwrap();
439		assert_eq!(body, "Hello");
440	}
441
442	#[tokio::test]
443	async fn test_middleware_basic() {
444		let handler = Arc::new(MockHandler {
445			response_body: "World".to_string(),
446		});
447
448		let middleware = MockMiddleware {
449			prefix: "Hello, ".to_string(),
450		};
451
452		let request = create_test_request();
453		let response = middleware.process(request, handler).await.unwrap();
454
455		let body = String::from_utf8(response.body.to_vec()).unwrap();
456		assert_eq!(body, "Hello, World");
457	}
458
459	#[tokio::test]
460	async fn test_middleware_chain_empty() {
461		let handler = Arc::new(MockHandler {
462			response_body: "Test".to_string(),
463		});
464
465		let chain = MiddlewareChain::new(handler);
466
467		let request = create_test_request();
468		let response = chain.handle(request).await.unwrap();
469
470		let body = String::from_utf8(response.body.to_vec()).unwrap();
471		assert_eq!(body, "Test");
472	}
473
474	#[tokio::test]
475	async fn test_middleware_chain_single() {
476		let handler = Arc::new(MockHandler {
477			response_body: "Handler".to_string(),
478		});
479
480		let middleware1 = Arc::new(MockMiddleware {
481			prefix: "MW1:".to_string(),
482		});
483
484		let chain = MiddlewareChain::new(handler).with_middleware(middleware1);
485
486		let request = create_test_request();
487		let response = chain.handle(request).await.unwrap();
488
489		let body = String::from_utf8(response.body.to_vec()).unwrap();
490		assert_eq!(body, "MW1:Handler");
491	}
492
493	#[tokio::test]
494	async fn test_middleware_chain_multiple() {
495		let handler = Arc::new(MockHandler {
496			response_body: "Data".to_string(),
497		});
498
499		let middleware1 = Arc::new(MockMiddleware {
500			prefix: "M1:".to_string(),
501		});
502
503		let middleware2 = Arc::new(MockMiddleware {
504			prefix: "M2:".to_string(),
505		});
506
507		let chain = MiddlewareChain::new(handler)
508			.with_middleware(middleware1)
509			.with_middleware(middleware2);
510
511		let request = create_test_request();
512		let response = chain.handle(request).await.unwrap();
513
514		let body = String::from_utf8(response.body.to_vec()).unwrap();
515		// Middleware are applied in the order they were added
516		assert_eq!(body, "M1:M2:Data");
517	}
518
519	#[tokio::test]
520	async fn test_middleware_chain_add_middleware() {
521		let handler = Arc::new(MockHandler {
522			response_body: "Result".to_string(),
523		});
524
525		let middleware = Arc::new(MockMiddleware {
526			prefix: "Prefix:".to_string(),
527		});
528
529		let mut chain = MiddlewareChain::new(handler);
530		chain.add_middleware(middleware);
531
532		let request = create_test_request();
533		let response = chain.handle(request).await.unwrap();
534
535		let body = String::from_utf8(response.body.to_vec()).unwrap();
536		assert_eq!(body, "Prefix:Result");
537	}
538
539	// Conditional middleware that only runs for /api/* paths
540	struct ConditionalMiddleware {
541		prefix: String,
542	}
543
544	#[async_trait]
545	impl Middleware for ConditionalMiddleware {
546		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
547			let response = next.handle(request).await?;
548			let current_body = String::from_utf8(response.body.to_vec()).unwrap_or_default();
549			let new_body = format!("{}{}", self.prefix, current_body);
550			Ok(Response::ok().with_body(new_body))
551		}
552
553		fn should_continue(&self, request: &Request) -> bool {
554			request.uri.path().starts_with("/api/")
555		}
556	}
557
558	#[tokio::test]
559	async fn test_middleware_conditional_skip() {
560		let handler = Arc::new(MockHandler {
561			response_body: "Response".to_string(),
562		});
563
564		let conditional_mw = Arc::new(ConditionalMiddleware {
565			prefix: "API:".to_string(),
566		});
567
568		let chain = MiddlewareChain::new(handler).with_middleware(conditional_mw);
569
570		// Test with /api/ path - middleware should run
571		let api_request = Request::builder()
572			.method(Method::GET)
573			.uri("/api/users")
574			.version(Version::HTTP_11)
575			.headers(HeaderMap::new())
576			.body(Bytes::new())
577			.build()
578			.unwrap();
579		let response = chain.handle(api_request).await.unwrap();
580		let body = String::from_utf8(response.body.to_vec()).unwrap();
581		assert_eq!(body, "API:Response");
582
583		// Test with non-/api/ path - middleware should be skipped
584		let non_api_request = Request::builder()
585			.method(Method::GET)
586			.uri("/public")
587			.version(Version::HTTP_11)
588			.headers(HeaderMap::new())
589			.body(Bytes::new())
590			.build()
591			.unwrap();
592		let response = chain.handle(non_api_request).await.unwrap();
593		let body = String::from_utf8(response.body.to_vec()).unwrap();
594		assert_eq!(body, "Response"); // No prefix because middleware was skipped
595	}
596
597	// Middleware that returns early with stop_chain=true
598	struct ShortCircuitMiddleware {
599		should_stop: bool,
600	}
601
602	#[async_trait]
603	impl Middleware for ShortCircuitMiddleware {
604		async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
605			if self.should_stop {
606				// Return early without calling next
607				return Ok(Response::unauthorized()
608					.with_body("Auth required")
609					.with_stop_chain(true));
610			}
611			next.handle(request).await
612		}
613	}
614
615	#[tokio::test]
616	async fn test_middleware_short_circuit() {
617		let handler = Arc::new(MockHandler {
618			response_body: "Handler Response".to_string(),
619		});
620
621		let short_circuit_mw = Arc::new(ShortCircuitMiddleware { should_stop: true });
622		let normal_mw = Arc::new(MockMiddleware {
623			prefix: "Normal:".to_string(),
624		});
625
626		let chain = MiddlewareChain::new(handler)
627			.with_middleware(short_circuit_mw)
628			.with_middleware(normal_mw);
629
630		let request = create_test_request();
631		let response = chain.handle(request).await.unwrap();
632
633		// Should get unauthorized response, not the handler response
634		assert_eq!(response.status, hyper::StatusCode::UNAUTHORIZED);
635		let body = String::from_utf8(response.body.to_vec()).unwrap();
636		assert_eq!(body, "Auth required");
637	}
638
639	#[tokio::test]
640	async fn test_middleware_no_short_circuit() {
641		let handler = Arc::new(MockHandler {
642			response_body: "Handler Response".to_string(),
643		});
644
645		let short_circuit_mw = Arc::new(ShortCircuitMiddleware { should_stop: false });
646		let normal_mw = Arc::new(MockMiddleware {
647			prefix: "Normal:".to_string(),
648		});
649
650		let chain = MiddlewareChain::new(handler)
651			.with_middleware(short_circuit_mw)
652			.with_middleware(normal_mw);
653
654		let request = create_test_request();
655		let response = chain.handle(request).await.unwrap();
656
657		// Should pass through to handler and apply normal middleware
658		assert_eq!(response.status, hyper::StatusCode::OK);
659		let body = String::from_utf8(response.body.to_vec()).unwrap();
660		assert_eq!(body, "Normal:Handler Response");
661	}
662
663	#[tokio::test]
664	async fn test_middleware_multiple_conditions() {
665		let handler = Arc::new(MockHandler {
666			response_body: "Base".to_string(),
667		});
668
669		// Only runs for /api/* paths
670		let api_mw = Arc::new(ConditionalMiddleware {
671			prefix: "API:".to_string(),
672		});
673
674		// Always runs
675		let always_mw = Arc::new(MockMiddleware {
676			prefix: "Always:".to_string(),
677		});
678
679		let chain = MiddlewareChain::new(handler)
680			.with_middleware(api_mw)
681			.with_middleware(always_mw);
682
683		// Test with /api/ path - both middleware should run
684		let api_request = Request::builder()
685			.method(Method::GET)
686			.uri("/api/test")
687			.version(Version::HTTP_11)
688			.headers(HeaderMap::new())
689			.body(Bytes::new())
690			.build()
691			.unwrap();
692		let response = chain.handle(api_request).await.unwrap();
693		let body = String::from_utf8(response.body.to_vec()).unwrap();
694		assert_eq!(body, "API:Always:Base");
695
696		// Test with non-/api/ path - only always_mw should run
697		let non_api_request = Request::builder()
698			.method(Method::GET)
699			.uri("/public")
700			.version(Version::HTTP_11)
701			.headers(HeaderMap::new())
702			.body(Bytes::new())
703			.build()
704			.unwrap();
705		let response = chain.handle(non_api_request).await.unwrap();
706		let body = String::from_utf8(response.body.to_vec()).unwrap();
707		assert_eq!(body, "Always:Base"); // Only always_mw prefix
708	}
709
710	#[tokio::test]
711	async fn test_response_should_stop_chain() {
712		let response = Response::ok();
713		assert!(!response.should_stop_chain());
714
715		let stopping_response = Response::unauthorized().with_stop_chain(true);
716		assert!(stopping_response.should_stop_chain());
717	}
718
719	// --- ExcludeMiddleware tests ---
720
721	fn create_request_with_path(path: &str) -> Request {
722		Request::builder()
723			.method(Method::GET)
724			.uri(path)
725			.version(Version::HTTP_11)
726			.headers(HeaderMap::new())
727			.body(Bytes::new())
728			.build()
729			.unwrap()
730	}
731
732	#[rstest::rstest]
733	#[case("/api/auth/login", true)]
734	#[case("/api/auth/register", true)]
735	#[case("/api/auth/", true)]
736	#[case("/api/users", false)]
737	#[case("/public", false)]
738	fn test_exclude_middleware_prefix_match(#[case] path: &str, #[case] should_exclude: bool) {
739		// Arrange
740		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
741			prefix: "MW:".to_string(),
742		});
743		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/api/auth/");
744
745		// Act
746		let request = create_request_with_path(path);
747		let result = exclude_mw.should_continue(&request);
748
749		// Assert
750		assert_eq!(result, !should_exclude);
751	}
752
753	#[rstest::rstest]
754	#[case("/health", true)]
755	#[case("/health/check", false)]
756	#[case("/healthz", false)]
757	#[case("/api/health", false)]
758	fn test_exclude_middleware_exact_match(#[case] path: &str, #[case] should_exclude: bool) {
759		// Arrange
760		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
761			prefix: "MW:".to_string(),
762		});
763		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/health");
764
765		// Act
766		let request = create_request_with_path(path);
767		let result = exclude_mw.should_continue(&request);
768
769		// Assert
770		assert_eq!(result, !should_exclude);
771	}
772
773	#[rstest::rstest]
774	fn test_exclude_middleware_no_match_passes_through() {
775		// Arrange
776		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
777			prefix: "MW:".to_string(),
778		});
779		let exclude_mw = ExcludeMiddleware::new(inner)
780			.add_exclusion("/api/auth/")
781			.add_exclusion("/health");
782
783		// Act
784		let request = create_request_with_path("/api/users");
785		let result = exclude_mw.should_continue(&request);
786
787		// Assert
788		assert!(result);
789	}
790
791	#[rstest::rstest]
792	#[tokio::test]
793	async fn test_exclude_middleware_delegates_process() {
794		// Arrange
795		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
796			prefix: "INNER:".to_string(),
797		});
798		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/excluded/");
799
800		let handler = Arc::new(MockHandler {
801			response_body: "Response".to_string(),
802		});
803
804		// Act
805		let request = create_request_with_path("/api/test");
806		let response = exclude_mw.process(request, handler).await.unwrap();
807
808		// Assert
809		let body = String::from_utf8(response.body.to_vec()).unwrap();
810		assert_eq!(body, "INNER:Response");
811	}
812
813	#[rstest::rstest]
814	fn test_exclude_middleware_multiple_exclusions() {
815		// Arrange
816		let inner: Arc<dyn Middleware> = Arc::new(MockMiddleware {
817			prefix: "MW:".to_string(),
818		});
819		let mut exclude_mw = ExcludeMiddleware::new(inner);
820		exclude_mw.add_exclusion_mut("/api/auth/");
821		exclude_mw.add_exclusion_mut("/admin/");
822		exclude_mw.add_exclusion_mut("/health");
823
824		// Act & Assert
825		assert!(!exclude_mw.should_continue(&create_request_with_path("/api/auth/login")));
826		assert!(!exclude_mw.should_continue(&create_request_with_path("/admin/dashboard")));
827		assert!(!exclude_mw.should_continue(&create_request_with_path("/health")));
828		assert!(exclude_mw.should_continue(&create_request_with_path("/api/users")));
829	}
830
831	#[rstest::rstest]
832	fn test_exclude_middleware_respects_inner_should_continue() {
833		// Arrange - inner middleware that rejects non-/api/ paths
834		let inner: Arc<dyn Middleware> = Arc::new(ConditionalMiddleware {
835			prefix: "API:".to_string(),
836		});
837		let exclude_mw = ExcludeMiddleware::new(inner).add_exclusion("/api/auth/");
838
839		// Act & Assert
840		// Excluded path -> false (excluded by wrapper)
841		assert!(!exclude_mw.should_continue(&create_request_with_path("/api/auth/login")));
842		// Non-excluded, but inner rejects non-/api/ -> false (inner's should_continue)
843		assert!(!exclude_mw.should_continue(&create_request_with_path("/public")));
844		// Non-excluded, inner accepts /api/ -> true
845		assert!(exclude_mw.should_continue(&create_request_with_path("/api/users")));
846	}
847}