kit_rs/routing/
router.rs

1use crate::http::{Request, Response};
2use crate::middleware::{into_boxed, BoxedMiddleware, Middleware};
3use matchit::Router as MatchitRouter;
4use std::collections::HashMap;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::{Arc, OnceLock, RwLock};
8
9/// Global registry mapping route names to path patterns
10static ROUTE_REGISTRY: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
11
12/// Register a route name -> path mapping
13pub fn register_route_name(name: &str, path: &str) {
14    let registry = ROUTE_REGISTRY.get_or_init(|| RwLock::new(HashMap::new()));
15    if let Ok(mut map) = registry.write() {
16        map.insert(name.to_string(), path.to_string());
17    }
18}
19
20/// Generate a URL for a named route with parameters
21///
22/// # Arguments
23/// * `name` - The route name (e.g., "users.show")
24/// * `params` - Slice of (key, value) tuples for path parameters
25///
26/// # Returns
27/// * `Some(String)` - The generated URL with parameters substituted
28/// * `None` - If the route name is not found
29///
30/// # Example
31/// ```
32/// let url = route("users.show", &[("id", "123")]);
33/// assert_eq!(url, Some("/users/123".to_string()));
34/// ```
35pub fn route(name: &str, params: &[(&str, &str)]) -> Option<String> {
36    let registry = ROUTE_REGISTRY.get()?.read().ok()?;
37    let path_pattern = registry.get(name)?;
38
39    let mut url = path_pattern.clone();
40    for (key, value) in params {
41        url = url.replace(&format!("{{{}}}", key), value);
42    }
43    Some(url)
44}
45
46/// Generate URL with HashMap parameters (used internally by Redirect)
47pub fn route_with_params(name: &str, params: &HashMap<String, String>) -> Option<String> {
48    let registry = ROUTE_REGISTRY.get()?.read().ok()?;
49    let path_pattern = registry.get(name)?;
50
51    let mut url = path_pattern.clone();
52    for (key, value) in params {
53        url = url.replace(&format!("{{{}}}", key), value);
54    }
55    Some(url)
56}
57
58/// HTTP method for tracking the last registered route
59#[derive(Clone, Copy)]
60enum Method {
61    Get,
62    Post,
63    Put,
64    Delete,
65}
66
67/// Type alias for route handlers
68pub type BoxedHandler =
69    Box<dyn Fn(Request) -> Pin<Box<dyn Future<Output = Response> + Send>> + Send + Sync>;
70
71/// HTTP Router with Laravel-like route registration
72pub struct Router {
73    get_routes: MatchitRouter<Arc<BoxedHandler>>,
74    post_routes: MatchitRouter<Arc<BoxedHandler>>,
75    put_routes: MatchitRouter<Arc<BoxedHandler>>,
76    delete_routes: MatchitRouter<Arc<BoxedHandler>>,
77    /// Middleware assignments: path -> boxed middleware instances
78    route_middleware: HashMap<String, Vec<BoxedMiddleware>>,
79    /// Fallback handler for when no routes match (overrides default 404)
80    fallback_handler: Option<Arc<BoxedHandler>>,
81    /// Middleware for the fallback route
82    fallback_middleware: Vec<BoxedMiddleware>,
83}
84
85impl Router {
86    pub fn new() -> Self {
87        Self {
88            get_routes: MatchitRouter::new(),
89            post_routes: MatchitRouter::new(),
90            put_routes: MatchitRouter::new(),
91            delete_routes: MatchitRouter::new(),
92            route_middleware: HashMap::new(),
93            fallback_handler: None,
94            fallback_middleware: Vec::new(),
95        }
96    }
97
98    /// Get middleware for a specific route path
99    pub fn get_route_middleware(&self, path: &str) -> Vec<BoxedMiddleware> {
100        self.route_middleware.get(path).cloned().unwrap_or_default()
101    }
102
103    /// Register middleware for a path (internal use)
104    pub(crate) fn add_middleware(&mut self, path: &str, middleware: BoxedMiddleware) {
105        self.route_middleware
106            .entry(path.to_string())
107            .or_default()
108            .push(middleware);
109    }
110
111    /// Set the fallback handler for when no routes match
112    pub(crate) fn set_fallback(&mut self, handler: Arc<BoxedHandler>) {
113        self.fallback_handler = Some(handler);
114    }
115
116    /// Add middleware to the fallback route
117    pub(crate) fn add_fallback_middleware(&mut self, middleware: BoxedMiddleware) {
118        self.fallback_middleware.push(middleware);
119    }
120
121    /// Get the fallback handler and its middleware
122    pub fn get_fallback(&self) -> Option<(Arc<BoxedHandler>, Vec<BoxedMiddleware>)> {
123        self.fallback_handler
124            .as_ref()
125            .map(|h| (h.clone(), self.fallback_middleware.clone()))
126    }
127
128    /// Insert a GET route with a pre-boxed handler (internal use for groups)
129    pub(crate) fn insert_get(&mut self, path: &str, handler: Arc<BoxedHandler>) {
130        self.get_routes.insert(path, handler).ok();
131    }
132
133    /// Insert a POST route with a pre-boxed handler (internal use for groups)
134    pub(crate) fn insert_post(&mut self, path: &str, handler: Arc<BoxedHandler>) {
135        self.post_routes.insert(path, handler).ok();
136    }
137
138    /// Insert a PUT route with a pre-boxed handler (internal use for groups)
139    pub(crate) fn insert_put(&mut self, path: &str, handler: Arc<BoxedHandler>) {
140        self.put_routes.insert(path, handler).ok();
141    }
142
143    /// Insert a DELETE route with a pre-boxed handler (internal use for groups)
144    pub(crate) fn insert_delete(&mut self, path: &str, handler: Arc<BoxedHandler>) {
145        self.delete_routes.insert(path, handler).ok();
146    }
147
148    /// Register a GET route
149    pub fn get<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
150    where
151        H: Fn(Request) -> Fut + Send + Sync + 'static,
152        Fut: Future<Output = Response> + Send + 'static,
153    {
154        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
155        self.get_routes.insert(path, Arc::new(handler)).ok();
156        RouteBuilder {
157            router: self,
158            last_path: path.to_string(),
159            _last_method: Method::Get,
160        }
161    }
162
163    /// Register a POST route
164    pub fn post<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
165    where
166        H: Fn(Request) -> Fut + Send + Sync + 'static,
167        Fut: Future<Output = Response> + Send + 'static,
168    {
169        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
170        self.post_routes.insert(path, Arc::new(handler)).ok();
171        RouteBuilder {
172            router: self,
173            last_path: path.to_string(),
174            _last_method: Method::Post,
175        }
176    }
177
178    /// Register a PUT route
179    pub fn put<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
180    where
181        H: Fn(Request) -> Fut + Send + Sync + 'static,
182        Fut: Future<Output = Response> + Send + 'static,
183    {
184        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
185        self.put_routes.insert(path, Arc::new(handler)).ok();
186        RouteBuilder {
187            router: self,
188            last_path: path.to_string(),
189            _last_method: Method::Put,
190        }
191    }
192
193    /// Register a DELETE route
194    pub fn delete<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
195    where
196        H: Fn(Request) -> Fut + Send + Sync + 'static,
197        Fut: Future<Output = Response> + Send + 'static,
198    {
199        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
200        self.delete_routes.insert(path, Arc::new(handler)).ok();
201        RouteBuilder {
202            router: self,
203            last_path: path.to_string(),
204            _last_method: Method::Delete,
205        }
206    }
207
208    /// Match a request and return the handler with extracted params
209    pub fn match_route(
210        &self,
211        method: &hyper::Method,
212        path: &str,
213    ) -> Option<(Arc<BoxedHandler>, HashMap<String, String>)> {
214        let router = match *method {
215            hyper::Method::GET => &self.get_routes,
216            hyper::Method::POST => &self.post_routes,
217            hyper::Method::PUT => &self.put_routes,
218            hyper::Method::DELETE => &self.delete_routes,
219            _ => return None,
220        };
221
222        router.at(path).ok().map(|matched| {
223            let params: HashMap<String, String> = matched
224                .params
225                .iter()
226                .map(|(k, v)| (k.to_string(), v.to_string()))
227                .collect();
228            (matched.value.clone(), params)
229        })
230    }
231}
232
233impl Default for Router {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239/// Builder returned after registering a route, enabling .name() chaining
240pub struct RouteBuilder {
241    pub(crate) router: Router,
242    last_path: String,
243    #[allow(dead_code)]
244    _last_method: Method,
245}
246
247impl RouteBuilder {
248    /// Name the most recently registered route
249    pub fn name(self, name: &str) -> Router {
250        register_route_name(name, &self.last_path);
251        self.router
252    }
253
254    /// Apply middleware to the most recently registered route
255    ///
256    /// # Example
257    ///
258    /// ```rust,ignore
259    /// Router::new()
260    ///     .get("/admin", admin_handler).middleware(AuthMiddleware)
261    ///     .get("/api/users", users_handler).middleware(CorsMiddleware)
262    /// ```
263    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> RouteBuilder {
264        self.router
265            .add_middleware(&self.last_path, into_boxed(middleware));
266        self
267    }
268
269    /// Apply pre-boxed middleware to the most recently registered route
270    /// (Used internally by route macros)
271    pub fn middleware_boxed(mut self, middleware: BoxedMiddleware) -> RouteBuilder {
272        self.router
273            .route_middleware
274            .entry(self.last_path.clone())
275            .or_default()
276            .push(middleware);
277        self
278    }
279
280    /// Register a GET route (for chaining without .name())
281    pub fn get<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
282    where
283        H: Fn(Request) -> Fut + Send + Sync + 'static,
284        Fut: Future<Output = Response> + Send + 'static,
285    {
286        self.router.get(path, handler)
287    }
288
289    /// Register a POST route (for chaining without .name())
290    pub fn post<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
291    where
292        H: Fn(Request) -> Fut + Send + Sync + 'static,
293        Fut: Future<Output = Response> + Send + 'static,
294    {
295        self.router.post(path, handler)
296    }
297
298    /// Register a PUT route (for chaining without .name())
299    pub fn put<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
300    where
301        H: Fn(Request) -> Fut + Send + Sync + 'static,
302        Fut: Future<Output = Response> + Send + 'static,
303    {
304        self.router.put(path, handler)
305    }
306
307    /// Register a DELETE route (for chaining without .name())
308    pub fn delete<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
309    where
310        H: Fn(Request) -> Fut + Send + Sync + 'static,
311        Fut: Future<Output = Response> + Send + 'static,
312    {
313        self.router.delete(path, handler)
314    }
315}
316
317impl From<RouteBuilder> for Router {
318    fn from(builder: RouteBuilder) -> Self {
319        builder.router
320    }
321}