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    pub fn new() -> Self {
186        Self {
187            get_routes: MatchitRouter::new(),
188            post_routes: MatchitRouter::new(),
189            put_routes: MatchitRouter::new(),
190            patch_routes: MatchitRouter::new(),
191            delete_routes: MatchitRouter::new(),
192            route_middleware: HashMap::new(),
193            fallback_handler: None,
194            fallback_middleware: Vec::new(),
195        }
196    }
197
198    /// Get middleware for a specific route path
199    pub fn get_route_middleware(&self, path: &str) -> Vec<BoxedMiddleware> {
200        self.route_middleware.get(path).cloned().unwrap_or_default()
201    }
202
203    /// Register middleware for a path (internal use)
204    pub(crate) fn add_middleware(&mut self, path: &str, middleware: BoxedMiddleware) {
205        self.route_middleware
206            .entry(path.to_string())
207            .or_default()
208            .push(middleware);
209    }
210
211    /// Set the fallback handler for when no routes match
212    pub(crate) fn set_fallback(&mut self, handler: Arc<BoxedHandler>) {
213        self.fallback_handler = Some(handler);
214    }
215
216    /// Add middleware to the fallback route
217    pub(crate) fn add_fallback_middleware(&mut self, middleware: BoxedMiddleware) {
218        self.fallback_middleware.push(middleware);
219    }
220
221    /// Get the fallback handler and its middleware
222    pub fn get_fallback(&self) -> Option<(Arc<BoxedHandler>, Vec<BoxedMiddleware>)> {
223        self.fallback_handler
224            .as_ref()
225            .map(|h| (h.clone(), self.fallback_middleware.clone()))
226    }
227
228    /// Insert a GET route with a pre-boxed handler (internal use for groups)
229    pub(crate) fn insert_get(&mut self, path: &str, handler: Arc<BoxedHandler>) {
230        self.get_routes
231            .insert(path, (handler, path.to_string()))
232            .ok();
233        register_route("GET", path);
234    }
235
236    /// Insert a POST route with a pre-boxed handler (internal use for groups)
237    pub(crate) fn insert_post(&mut self, path: &str, handler: Arc<BoxedHandler>) {
238        self.post_routes
239            .insert(path, (handler, path.to_string()))
240            .ok();
241        register_route("POST", path);
242    }
243
244    /// Insert a PUT route with a pre-boxed handler (internal use for groups)
245    pub(crate) fn insert_put(&mut self, path: &str, handler: Arc<BoxedHandler>) {
246        self.put_routes
247            .insert(path, (handler, path.to_string()))
248            .ok();
249        register_route("PUT", path);
250    }
251
252    /// Insert a PATCH route with a pre-boxed handler (internal use for groups)
253    pub(crate) fn insert_patch(&mut self, path: &str, handler: Arc<BoxedHandler>) {
254        self.patch_routes
255            .insert(path, (handler, path.to_string()))
256            .ok();
257        register_route("PATCH", path);
258    }
259
260    /// Insert a DELETE route with a pre-boxed handler (internal use for groups)
261    pub(crate) fn insert_delete(&mut self, path: &str, handler: Arc<BoxedHandler>) {
262        self.delete_routes
263            .insert(path, (handler, path.to_string()))
264            .ok();
265        register_route("DELETE", path);
266    }
267
268    /// Register a GET route
269    pub fn get<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
270    where
271        H: Fn(Request) -> Fut + Send + Sync + 'static,
272        Fut: Future<Output = Response> + Send + 'static,
273    {
274        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
275        self.get_routes
276            .insert(path, (Arc::new(handler), path.to_string()))
277            .ok();
278        register_route("GET", path);
279        RouteBuilder {
280            router: self,
281            last_path: path.to_string(),
282            _last_method: Method::Get,
283        }
284    }
285
286    /// Register a POST route
287    pub fn post<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
288    where
289        H: Fn(Request) -> Fut + Send + Sync + 'static,
290        Fut: Future<Output = Response> + Send + 'static,
291    {
292        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
293        self.post_routes
294            .insert(path, (Arc::new(handler), path.to_string()))
295            .ok();
296        register_route("POST", path);
297        RouteBuilder {
298            router: self,
299            last_path: path.to_string(),
300            _last_method: Method::Post,
301        }
302    }
303
304    /// Register a PUT route
305    pub fn put<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
306    where
307        H: Fn(Request) -> Fut + Send + Sync + 'static,
308        Fut: Future<Output = Response> + Send + 'static,
309    {
310        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
311        self.put_routes
312            .insert(path, (Arc::new(handler), path.to_string()))
313            .ok();
314        register_route("PUT", path);
315        RouteBuilder {
316            router: self,
317            last_path: path.to_string(),
318            _last_method: Method::Put,
319        }
320    }
321
322    /// Register a PATCH route
323    pub fn patch<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
324    where
325        H: Fn(Request) -> Fut + Send + Sync + 'static,
326        Fut: Future<Output = Response> + Send + 'static,
327    {
328        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
329        self.patch_routes
330            .insert(path, (Arc::new(handler), path.to_string()))
331            .ok();
332        register_route("PATCH", path);
333        RouteBuilder {
334            router: self,
335            last_path: path.to_string(),
336            _last_method: Method::Patch,
337        }
338    }
339
340    /// Register a DELETE route
341    pub fn delete<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
342    where
343        H: Fn(Request) -> Fut + Send + Sync + 'static,
344        Fut: Future<Output = Response> + Send + 'static,
345    {
346        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
347        self.delete_routes
348            .insert(path, (Arc::new(handler), path.to_string()))
349            .ok();
350        register_route("DELETE", path);
351        RouteBuilder {
352            router: self,
353            last_path: path.to_string(),
354            _last_method: Method::Delete,
355        }
356    }
357
358    /// Match a request and return the handler with extracted params and route pattern
359    ///
360    /// Returns (handler, params, route_pattern) where route_pattern is the original
361    /// pattern like "/users/{id}" for metrics grouping.
362    pub fn match_route(
363        &self,
364        method: &hyper::Method,
365        path: &str,
366    ) -> Option<(Arc<BoxedHandler>, HashMap<String, String>, String)> {
367        let router = match *method {
368            hyper::Method::GET => &self.get_routes,
369            hyper::Method::POST => &self.post_routes,
370            hyper::Method::PUT => &self.put_routes,
371            hyper::Method::PATCH => &self.patch_routes,
372            hyper::Method::DELETE => &self.delete_routes,
373            _ => return None,
374        };
375
376        router.at(path).ok().map(|matched| {
377            let params: HashMap<String, String> = matched
378                .params
379                .iter()
380                .map(|(k, v)| (k.to_string(), v.to_string()))
381                .collect();
382            let (handler, pattern) = matched.value.clone();
383            (handler, params, pattern)
384        })
385    }
386}
387
388impl Default for Router {
389    fn default() -> Self {
390        Self::new()
391    }
392}
393
394/// Builder returned after registering a route, enabling .name() chaining
395pub struct RouteBuilder {
396    pub(crate) router: Router,
397    last_path: String,
398    #[allow(dead_code)]
399    _last_method: Method,
400}
401
402impl RouteBuilder {
403    /// Name the most recently registered route
404    pub fn name(self, name: &str) -> Router {
405        register_route_name(name, &self.last_path);
406        self.router
407    }
408
409    /// Apply middleware to the most recently registered route
410    ///
411    /// # Example
412    ///
413    /// ```rust,ignore
414    /// Router::new()
415    ///     .get("/admin", admin_handler).middleware(AuthMiddleware)
416    ///     .get("/api/users", users_handler).middleware(CorsMiddleware)
417    /// ```
418    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> RouteBuilder {
419        // Track middleware name for introspection
420        let type_name = std::any::type_name::<M>();
421        let short_name = type_name.rsplit("::").next().unwrap_or(type_name);
422        update_route_middleware(&self.last_path, short_name);
423
424        self.router
425            .add_middleware(&self.last_path, into_boxed(middleware));
426        self
427    }
428
429    /// Apply pre-boxed middleware to the most recently registered route
430    /// (Used internally by route macros)
431    pub fn middleware_boxed(mut self, middleware: BoxedMiddleware) -> RouteBuilder {
432        // Track middleware (name not available for boxed middleware)
433        update_route_middleware(&self.last_path, "BoxedMiddleware");
434
435        self.router
436            .route_middleware
437            .entry(self.last_path.clone())
438            .or_default()
439            .push(middleware);
440        self
441    }
442
443    /// Register a GET route (for chaining without .name())
444    pub fn get<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
445    where
446        H: Fn(Request) -> Fut + Send + Sync + 'static,
447        Fut: Future<Output = Response> + Send + 'static,
448    {
449        self.router.get(path, handler)
450    }
451
452    /// Register a POST route (for chaining without .name())
453    pub fn post<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
454    where
455        H: Fn(Request) -> Fut + Send + Sync + 'static,
456        Fut: Future<Output = Response> + Send + 'static,
457    {
458        self.router.post(path, handler)
459    }
460
461    /// Register a PUT route (for chaining without .name())
462    pub fn put<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
463    where
464        H: Fn(Request) -> Fut + Send + Sync + 'static,
465        Fut: Future<Output = Response> + Send + 'static,
466    {
467        self.router.put(path, handler)
468    }
469
470    /// Register a PATCH route (for chaining without .name())
471    pub fn patch<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
472    where
473        H: Fn(Request) -> Fut + Send + Sync + 'static,
474        Fut: Future<Output = Response> + Send + 'static,
475    {
476        self.router.patch(path, handler)
477    }
478
479    /// Register a DELETE route (for chaining without .name())
480    pub fn delete<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
481    where
482        H: Fn(Request) -> Fut + Send + Sync + 'static,
483        Fut: Future<Output = Response> + Send + 'static,
484    {
485        self.router.delete(path, handler)
486    }
487}
488
489impl From<RouteBuilder> for Router {
490    fn from(builder: RouteBuilder) -> Self {
491        builder.router
492    }
493}