Skip to main content

ferro_rs/routing/
router.rs

1use crate::http::{Request, Response};
2use crate::middleware::{into_boxed, BoxedMiddleware, Middleware};
3use matchit::Router as MatchitRouter;
4use serde::Serialize;
5use std::collections::HashMap;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::{Arc, OnceLock, RwLock};
9
10/// Global registry mapping route names to path patterns
11static ROUTE_REGISTRY: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
12
13/// Global registry of all registered routes for introspection
14static REGISTERED_ROUTES: OnceLock<RwLock<Vec<RouteInfo>>> = OnceLock::new();
15
16/// Information about a registered route for introspection
17#[derive(Debug, Clone, Serialize)]
18pub struct RouteInfo {
19    /// HTTP method (GET, POST, PUT, DELETE)
20    pub method: String,
21    /// Route path pattern (e.g., "/users/{id}")
22    pub path: String,
23    /// Optional route name (e.g., "users.show")
24    pub name: Option<String>,
25    /// Middleware applied to this route
26    pub middleware: Vec<String>,
27}
28
29/// Register a route for introspection
30fn register_route(method: &str, path: &str) {
31    let registry = REGISTERED_ROUTES.get_or_init(|| RwLock::new(Vec::new()));
32    if let Ok(mut routes) = registry.write() {
33        routes.push(RouteInfo {
34            method: method.to_string(),
35            path: path.to_string(),
36            name: None,
37            middleware: Vec::new(),
38        });
39    }
40}
41
42/// Update the most recently registered route with its name
43fn update_route_name(path: &str, name: &str) {
44    let registry = REGISTERED_ROUTES.get_or_init(|| RwLock::new(Vec::new()));
45    if let Ok(mut routes) = registry.write() {
46        // Find the most recent route with this path and update its name
47        if let Some(route) = routes.iter_mut().rev().find(|r| r.path == path) {
48            route.name = Some(name.to_string());
49        }
50    }
51}
52
53/// Update a route with middleware name
54fn update_route_middleware(path: &str, middleware_name: &str) {
55    let registry = REGISTERED_ROUTES.get_or_init(|| RwLock::new(Vec::new()));
56    if let Ok(mut routes) = registry.write() {
57        // Find the most recent route with this path and add middleware
58        if let Some(route) = routes.iter_mut().rev().find(|r| r.path == path) {
59            route.middleware.push(middleware_name.to_string());
60        }
61    }
62}
63
64/// Get all registered routes for introspection
65pub fn get_registered_routes() -> Vec<RouteInfo> {
66    REGISTERED_ROUTES
67        .get()
68        .and_then(|r| r.read().ok())
69        .map(|routes| routes.clone())
70        .unwrap_or_default()
71}
72
73/// Register a route name -> path mapping
74pub fn register_route_name(name: &str, path: &str) {
75    let registry = ROUTE_REGISTRY.get_or_init(|| RwLock::new(HashMap::new()));
76    if let Ok(mut map) = registry.write() {
77        map.insert(name.to_string(), path.to_string());
78    }
79    // Also update the introspection registry
80    update_route_name(path, name);
81}
82
83/// Generate a URL for a named route with parameters
84///
85/// # Arguments
86/// * `name` - The route name (e.g., "users.show")
87/// * `params` - Slice of (key, value) tuples for path parameters
88///
89/// # Returns
90/// * `Some(String)` - The generated URL with parameters substituted
91/// * `None` - If the route name is not found
92///
93/// # Example
94/// ```ignore
95/// let url = route("users.show", &[("id", "123")]);
96/// assert_eq!(url, Some("/users/123".to_string()));
97/// ```
98pub fn route(name: &str, params: &[(&str, &str)]) -> Option<String> {
99    let registry = ROUTE_REGISTRY.get()?.read().ok()?;
100    let path_pattern = registry.get(name)?;
101
102    let mut url = path_pattern.clone();
103    for (key, value) in params {
104        url = url.replace(&format!("{{{key}}}"), value);
105    }
106    Some(url)
107}
108
109/// Generate URL with HashMap parameters (used internally by Redirect)
110pub fn route_with_params(name: &str, params: &HashMap<String, String>) -> Option<String> {
111    let registry = ROUTE_REGISTRY.get()?.read().ok()?;
112    let path_pattern = registry.get(name)?;
113
114    let mut url = path_pattern.clone();
115    for (key, value) in params {
116        url = url.replace(&format!("{{{key}}}"), value);
117    }
118    Some(url)
119}
120
121/// HTTP method for tracking the last registered route
122#[derive(Clone, Copy)]
123enum Method {
124    Get,
125    Post,
126    Put,
127    Patch,
128    Delete,
129}
130
131/// Type alias for route handlers
132pub type BoxedHandler =
133    Box<dyn Fn(Request) -> Pin<Box<dyn Future<Output = Response> + Send>> + Send + Sync>;
134
135/// Value stored in the router: handler + pattern for metrics
136type RouteValue = (Arc<BoxedHandler>, String);
137
138/// HTTP Router with Laravel-like route registration
139pub struct Router {
140    get_routes: MatchitRouter<RouteValue>,
141    post_routes: MatchitRouter<RouteValue>,
142    put_routes: MatchitRouter<RouteValue>,
143    patch_routes: MatchitRouter<RouteValue>,
144    delete_routes: MatchitRouter<RouteValue>,
145    /// Middleware assignments: path -> boxed middleware instances
146    route_middleware: HashMap<String, Vec<BoxedMiddleware>>,
147    /// Fallback handler for when no routes match (overrides default 404)
148    fallback_handler: Option<Arc<BoxedHandler>>,
149    /// Middleware for the fallback route
150    fallback_middleware: Vec<BoxedMiddleware>,
151}
152
153impl Router {
154    pub fn new() -> Self {
155        Self {
156            get_routes: MatchitRouter::new(),
157            post_routes: MatchitRouter::new(),
158            put_routes: MatchitRouter::new(),
159            patch_routes: MatchitRouter::new(),
160            delete_routes: MatchitRouter::new(),
161            route_middleware: HashMap::new(),
162            fallback_handler: None,
163            fallback_middleware: Vec::new(),
164        }
165    }
166
167    /// Get middleware for a specific route path
168    pub fn get_route_middleware(&self, path: &str) -> Vec<BoxedMiddleware> {
169        self.route_middleware.get(path).cloned().unwrap_or_default()
170    }
171
172    /// Register middleware for a path (internal use)
173    pub(crate) fn add_middleware(&mut self, path: &str, middleware: BoxedMiddleware) {
174        self.route_middleware
175            .entry(path.to_string())
176            .or_default()
177            .push(middleware);
178    }
179
180    /// Set the fallback handler for when no routes match
181    pub(crate) fn set_fallback(&mut self, handler: Arc<BoxedHandler>) {
182        self.fallback_handler = Some(handler);
183    }
184
185    /// Add middleware to the fallback route
186    pub(crate) fn add_fallback_middleware(&mut self, middleware: BoxedMiddleware) {
187        self.fallback_middleware.push(middleware);
188    }
189
190    /// Get the fallback handler and its middleware
191    pub fn get_fallback(&self) -> Option<(Arc<BoxedHandler>, Vec<BoxedMiddleware>)> {
192        self.fallback_handler
193            .as_ref()
194            .map(|h| (h.clone(), self.fallback_middleware.clone()))
195    }
196
197    /// Insert a GET route with a pre-boxed handler (internal use for groups)
198    pub(crate) fn insert_get(&mut self, path: &str, handler: Arc<BoxedHandler>) {
199        self.get_routes
200            .insert(path, (handler, path.to_string()))
201            .ok();
202        register_route("GET", path);
203    }
204
205    /// Insert a POST route with a pre-boxed handler (internal use for groups)
206    pub(crate) fn insert_post(&mut self, path: &str, handler: Arc<BoxedHandler>) {
207        self.post_routes
208            .insert(path, (handler, path.to_string()))
209            .ok();
210        register_route("POST", path);
211    }
212
213    /// Insert a PUT route with a pre-boxed handler (internal use for groups)
214    pub(crate) fn insert_put(&mut self, path: &str, handler: Arc<BoxedHandler>) {
215        self.put_routes
216            .insert(path, (handler, path.to_string()))
217            .ok();
218        register_route("PUT", path);
219    }
220
221    /// Insert a PATCH route with a pre-boxed handler (internal use for groups)
222    pub(crate) fn insert_patch(&mut self, path: &str, handler: Arc<BoxedHandler>) {
223        self.patch_routes
224            .insert(path, (handler, path.to_string()))
225            .ok();
226        register_route("PATCH", path);
227    }
228
229    /// Insert a DELETE route with a pre-boxed handler (internal use for groups)
230    pub(crate) fn insert_delete(&mut self, path: &str, handler: Arc<BoxedHandler>) {
231        self.delete_routes
232            .insert(path, (handler, path.to_string()))
233            .ok();
234        register_route("DELETE", path);
235    }
236
237    /// Register a GET route
238    pub fn get<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
239    where
240        H: Fn(Request) -> Fut + Send + Sync + 'static,
241        Fut: Future<Output = Response> + Send + 'static,
242    {
243        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
244        self.get_routes
245            .insert(path, (Arc::new(handler), path.to_string()))
246            .ok();
247        register_route("GET", path);
248        RouteBuilder {
249            router: self,
250            last_path: path.to_string(),
251            _last_method: Method::Get,
252        }
253    }
254
255    /// Register a POST route
256    pub fn post<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
257    where
258        H: Fn(Request) -> Fut + Send + Sync + 'static,
259        Fut: Future<Output = Response> + Send + 'static,
260    {
261        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
262        self.post_routes
263            .insert(path, (Arc::new(handler), path.to_string()))
264            .ok();
265        register_route("POST", path);
266        RouteBuilder {
267            router: self,
268            last_path: path.to_string(),
269            _last_method: Method::Post,
270        }
271    }
272
273    /// Register a PUT route
274    pub fn put<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
275    where
276        H: Fn(Request) -> Fut + Send + Sync + 'static,
277        Fut: Future<Output = Response> + Send + 'static,
278    {
279        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
280        self.put_routes
281            .insert(path, (Arc::new(handler), path.to_string()))
282            .ok();
283        register_route("PUT", path);
284        RouteBuilder {
285            router: self,
286            last_path: path.to_string(),
287            _last_method: Method::Put,
288        }
289    }
290
291    /// Register a PATCH route
292    pub fn patch<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
293    where
294        H: Fn(Request) -> Fut + Send + Sync + 'static,
295        Fut: Future<Output = Response> + Send + 'static,
296    {
297        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
298        self.patch_routes
299            .insert(path, (Arc::new(handler), path.to_string()))
300            .ok();
301        register_route("PATCH", path);
302        RouteBuilder {
303            router: self,
304            last_path: path.to_string(),
305            _last_method: Method::Patch,
306        }
307    }
308
309    /// Register a DELETE route
310    pub fn delete<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
311    where
312        H: Fn(Request) -> Fut + Send + Sync + 'static,
313        Fut: Future<Output = Response> + Send + 'static,
314    {
315        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
316        self.delete_routes
317            .insert(path, (Arc::new(handler), path.to_string()))
318            .ok();
319        register_route("DELETE", path);
320        RouteBuilder {
321            router: self,
322            last_path: path.to_string(),
323            _last_method: Method::Delete,
324        }
325    }
326
327    /// Match a request and return the handler with extracted params and route pattern
328    ///
329    /// Returns (handler, params, route_pattern) where route_pattern is the original
330    /// pattern like "/users/{id}" for metrics grouping.
331    pub fn match_route(
332        &self,
333        method: &hyper::Method,
334        path: &str,
335    ) -> Option<(Arc<BoxedHandler>, HashMap<String, String>, String)> {
336        let router = match *method {
337            hyper::Method::GET => &self.get_routes,
338            hyper::Method::POST => &self.post_routes,
339            hyper::Method::PUT => &self.put_routes,
340            hyper::Method::PATCH => &self.patch_routes,
341            hyper::Method::DELETE => &self.delete_routes,
342            _ => return None,
343        };
344
345        router.at(path).ok().map(|matched| {
346            let params: HashMap<String, String> = matched
347                .params
348                .iter()
349                .map(|(k, v)| (k.to_string(), v.to_string()))
350                .collect();
351            let (handler, pattern) = matched.value.clone();
352            (handler, params, pattern)
353        })
354    }
355}
356
357impl Default for Router {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363/// Builder returned after registering a route, enabling .name() chaining
364pub struct RouteBuilder {
365    pub(crate) router: Router,
366    last_path: String,
367    #[allow(dead_code)]
368    _last_method: Method,
369}
370
371impl RouteBuilder {
372    /// Name the most recently registered route
373    pub fn name(self, name: &str) -> Router {
374        register_route_name(name, &self.last_path);
375        self.router
376    }
377
378    /// Apply middleware to the most recently registered route
379    ///
380    /// # Example
381    ///
382    /// ```rust,ignore
383    /// Router::new()
384    ///     .get("/admin", admin_handler).middleware(AuthMiddleware)
385    ///     .get("/api/users", users_handler).middleware(CorsMiddleware)
386    /// ```
387    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> RouteBuilder {
388        // Track middleware name for introspection
389        let type_name = std::any::type_name::<M>();
390        let short_name = type_name.rsplit("::").next().unwrap_or(type_name);
391        update_route_middleware(&self.last_path, short_name);
392
393        self.router
394            .add_middleware(&self.last_path, into_boxed(middleware));
395        self
396    }
397
398    /// Apply pre-boxed middleware to the most recently registered route
399    /// (Used internally by route macros)
400    pub fn middleware_boxed(mut self, middleware: BoxedMiddleware) -> RouteBuilder {
401        // Track middleware (name not available for boxed middleware)
402        update_route_middleware(&self.last_path, "BoxedMiddleware");
403
404        self.router
405            .route_middleware
406            .entry(self.last_path.clone())
407            .or_default()
408            .push(middleware);
409        self
410    }
411
412    /// Register a GET route (for chaining without .name())
413    pub fn get<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
414    where
415        H: Fn(Request) -> Fut + Send + Sync + 'static,
416        Fut: Future<Output = Response> + Send + 'static,
417    {
418        self.router.get(path, handler)
419    }
420
421    /// Register a POST route (for chaining without .name())
422    pub fn post<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
423    where
424        H: Fn(Request) -> Fut + Send + Sync + 'static,
425        Fut: Future<Output = Response> + Send + 'static,
426    {
427        self.router.post(path, handler)
428    }
429
430    /// Register a PUT route (for chaining without .name())
431    pub fn put<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
432    where
433        H: Fn(Request) -> Fut + Send + Sync + 'static,
434        Fut: Future<Output = Response> + Send + 'static,
435    {
436        self.router.put(path, handler)
437    }
438
439    /// Register a PATCH route (for chaining without .name())
440    pub fn patch<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
441    where
442        H: Fn(Request) -> Fut + Send + Sync + 'static,
443        Fut: Future<Output = Response> + Send + 'static,
444    {
445        self.router.patch(path, handler)
446    }
447
448    /// Register a DELETE route (for chaining without .name())
449    pub fn delete<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
450    where
451        H: Fn(Request) -> Fut + Send + Sync + 'static,
452        Fut: Future<Output = Response> + Send + 'static,
453    {
454        self.router.delete(path, handler)
455    }
456}
457
458impl From<RouteBuilder> for Router {
459    fn from(builder: RouteBuilder) -> Self {
460        builder.router
461    }
462}