Skip to main content

ferro_rs/middleware/
pre_route_registry.rs

1//! Pre-route middleware registry.
2//!
3//! Pre-route middleware runs before route matching, allowing path rewrites that
4//! affect which route handler is selected. Standard global middleware runs after
5//! route matching (inside the handler chain) and cannot influence routing.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use ferro::{pre_route_middleware, PreRouteMiddleware, Request, HttpResponse};
11//! use async_trait::async_trait;
12//!
13//! pub struct HostMiddleware;
14//!
15//! #[async_trait]
16//! impl PreRouteMiddleware for HostMiddleware {
17//!     async fn rewrite(&self, request: Request) -> Result<Request, HttpResponse> {
18//!         // Inspect Host header, call request.set_path() if needed, then:
19//!         Ok(request)
20//!         // Or to short-circuit:
21//!         // Err(HttpResponse::new().status(404).set_body("Not found"))
22//!     }
23//! }
24//!
25//! // In bootstrap.rs:
26//! pre_route_middleware!(HostMiddleware::new());
27//! ```
28
29use crate::http::{HttpResponse, Request};
30use async_trait::async_trait;
31use std::sync::RwLock;
32use std::sync::{Arc, OnceLock};
33
34/// Trait for middleware that runs before route matching.
35///
36/// Implement `rewrite` to inspect and/or rewrite the request path. The method
37/// receives ownership of the request and must return either the (possibly modified)
38/// request to continue routing, or an `HttpResponse` to short-circuit immediately.
39///
40/// Short-circuiting is appropriate for cases like unknown custom domains (return 404)
41/// where the request should never reach route matching.
42#[async_trait]
43pub trait PreRouteMiddleware: Send + Sync {
44    /// Inspect and optionally rewrite the request before route matching.
45    ///
46    /// - Return `Ok(request)` to continue (with or without path rewrite via `set_path`).
47    /// - Return `Err(response)` to short-circuit — the response is sent immediately
48    ///   and route matching is skipped.
49    async fn rewrite(&self, request: Request) -> Result<Request, HttpResponse>;
50}
51
52/// Type alias for a boxed pre-route middleware.
53pub type BoxedPreRouteMiddleware = Arc<dyn PreRouteMiddleware>;
54
55/// Global pre-route middleware registry.
56static PRE_ROUTE_MIDDLEWARE: OnceLock<RwLock<Vec<BoxedPreRouteMiddleware>>> = OnceLock::new();
57
58/// Register a pre-route middleware that runs before route matching on every request.
59///
60/// Called by the `pre_route_middleware!` macro. Middleware runs in registration order.
61pub fn register_pre_route_middleware<M: PreRouteMiddleware + 'static>(middleware: M) {
62    let registry = PRE_ROUTE_MIDDLEWARE.get_or_init(|| RwLock::new(Vec::new()));
63    if let Ok(mut vec) = registry.write() {
64        vec.push(Arc::new(middleware));
65    }
66}
67
68/// Get all registered pre-route middleware.
69///
70/// Used internally by `server.rs` before route matching.
71pub fn get_pre_route_middleware() -> Vec<BoxedPreRouteMiddleware> {
72    PRE_ROUTE_MIDDLEWARE
73        .get()
74        .and_then(|lock| lock.read().ok())
75        .map(|vec| vec.clone())
76        .unwrap_or_default()
77}