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}
80
81impl Router {
82    pub fn new() -> Self {
83        Self {
84            get_routes: MatchitRouter::new(),
85            post_routes: MatchitRouter::new(),
86            put_routes: MatchitRouter::new(),
87            delete_routes: MatchitRouter::new(),
88            route_middleware: HashMap::new(),
89        }
90    }
91
92    /// Get middleware for a specific route path
93    pub fn get_route_middleware(&self, path: &str) -> Vec<BoxedMiddleware> {
94        self.route_middleware.get(path).cloned().unwrap_or_default()
95    }
96
97    /// Register middleware for a path (internal use)
98    pub(crate) fn add_middleware(&mut self, path: &str, middleware: BoxedMiddleware) {
99        self.route_middleware
100            .entry(path.to_string())
101            .or_default()
102            .push(middleware);
103    }
104
105    /// Insert a GET route with a pre-boxed handler (internal use for groups)
106    pub(crate) fn insert_get(&mut self, path: &str, handler: Arc<BoxedHandler>) {
107        self.get_routes.insert(path, handler).ok();
108    }
109
110    /// Insert a POST route with a pre-boxed handler (internal use for groups)
111    pub(crate) fn insert_post(&mut self, path: &str, handler: Arc<BoxedHandler>) {
112        self.post_routes.insert(path, handler).ok();
113    }
114
115    /// Insert a PUT route with a pre-boxed handler (internal use for groups)
116    pub(crate) fn insert_put(&mut self, path: &str, handler: Arc<BoxedHandler>) {
117        self.put_routes.insert(path, handler).ok();
118    }
119
120    /// Insert a DELETE route with a pre-boxed handler (internal use for groups)
121    pub(crate) fn insert_delete(&mut self, path: &str, handler: Arc<BoxedHandler>) {
122        self.delete_routes.insert(path, handler).ok();
123    }
124
125    /// Register a GET route
126    pub fn get<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
127    where
128        H: Fn(Request) -> Fut + Send + Sync + 'static,
129        Fut: Future<Output = Response> + Send + 'static,
130    {
131        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
132        self.get_routes.insert(path, Arc::new(handler)).ok();
133        RouteBuilder {
134            router: self,
135            last_path: path.to_string(),
136            _last_method: Method::Get,
137        }
138    }
139
140    /// Register a POST route
141    pub fn post<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
142    where
143        H: Fn(Request) -> Fut + Send + Sync + 'static,
144        Fut: Future<Output = Response> + Send + 'static,
145    {
146        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
147        self.post_routes.insert(path, Arc::new(handler)).ok();
148        RouteBuilder {
149            router: self,
150            last_path: path.to_string(),
151            _last_method: Method::Post,
152        }
153    }
154
155    /// Register a PUT route
156    pub fn put<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
157    where
158        H: Fn(Request) -> Fut + Send + Sync + 'static,
159        Fut: Future<Output = Response> + Send + 'static,
160    {
161        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
162        self.put_routes.insert(path, Arc::new(handler)).ok();
163        RouteBuilder {
164            router: self,
165            last_path: path.to_string(),
166            _last_method: Method::Put,
167        }
168    }
169
170    /// Register a DELETE route
171    pub fn delete<H, Fut>(mut self, path: &str, handler: H) -> RouteBuilder
172    where
173        H: Fn(Request) -> Fut + Send + Sync + 'static,
174        Fut: Future<Output = Response> + Send + 'static,
175    {
176        let handler: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
177        self.delete_routes.insert(path, Arc::new(handler)).ok();
178        RouteBuilder {
179            router: self,
180            last_path: path.to_string(),
181            _last_method: Method::Delete,
182        }
183    }
184
185    /// Match a request and return the handler with extracted params
186    pub fn match_route(
187        &self,
188        method: &hyper::Method,
189        path: &str,
190    ) -> Option<(Arc<BoxedHandler>, HashMap<String, String>)> {
191        let router = match *method {
192            hyper::Method::GET => &self.get_routes,
193            hyper::Method::POST => &self.post_routes,
194            hyper::Method::PUT => &self.put_routes,
195            hyper::Method::DELETE => &self.delete_routes,
196            _ => return None,
197        };
198
199        router.at(path).ok().map(|matched| {
200            let params: HashMap<String, String> = matched
201                .params
202                .iter()
203                .map(|(k, v)| (k.to_string(), v.to_string()))
204                .collect();
205            (matched.value.clone(), params)
206        })
207    }
208}
209
210impl Default for Router {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216/// Builder returned after registering a route, enabling .name() chaining
217pub struct RouteBuilder {
218    pub(crate) router: Router,
219    last_path: String,
220    #[allow(dead_code)]
221    _last_method: Method,
222}
223
224impl RouteBuilder {
225    /// Name the most recently registered route
226    pub fn name(self, name: &str) -> Router {
227        register_route_name(name, &self.last_path);
228        self.router
229    }
230
231    /// Apply middleware to the most recently registered route
232    ///
233    /// # Example
234    ///
235    /// ```rust,ignore
236    /// Router::new()
237    ///     .get("/admin", admin_handler).middleware(AuthMiddleware)
238    ///     .get("/api/users", users_handler).middleware(CorsMiddleware)
239    /// ```
240    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> RouteBuilder {
241        self.router
242            .add_middleware(&self.last_path, into_boxed(middleware));
243        self
244    }
245
246    /// Apply pre-boxed middleware to the most recently registered route
247    /// (Used internally by route macros)
248    pub fn middleware_boxed(mut self, middleware: BoxedMiddleware) -> RouteBuilder {
249        self.router
250            .route_middleware
251            .entry(self.last_path.clone())
252            .or_default()
253            .push(middleware);
254        self
255    }
256
257    /// Register a GET route (for chaining without .name())
258    pub fn get<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
259    where
260        H: Fn(Request) -> Fut + Send + Sync + 'static,
261        Fut: Future<Output = Response> + Send + 'static,
262    {
263        self.router.get(path, handler)
264    }
265
266    /// Register a POST route (for chaining without .name())
267    pub fn post<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
268    where
269        H: Fn(Request) -> Fut + Send + Sync + 'static,
270        Fut: Future<Output = Response> + Send + 'static,
271    {
272        self.router.post(path, handler)
273    }
274
275    /// Register a PUT route (for chaining without .name())
276    pub fn put<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
277    where
278        H: Fn(Request) -> Fut + Send + Sync + 'static,
279        Fut: Future<Output = Response> + Send + 'static,
280    {
281        self.router.put(path, handler)
282    }
283
284    /// Register a DELETE route (for chaining without .name())
285    pub fn delete<H, Fut>(self, path: &str, handler: H) -> RouteBuilder
286    where
287        H: Fn(Request) -> Fut + Send + Sync + 'static,
288        Fut: Future<Output = Response> + Send + 'static,
289    {
290        self.router.delete(path, handler)
291    }
292}
293
294impl From<RouteBuilder> for Router {
295    fn from(builder: RouteBuilder) -> Self {
296        builder.router
297    }
298}