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