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