kit_rs/routing/
macros.rs

1//! Route definition macros and helpers for Laravel-like routing syntax
2//!
3//! This module provides a clean, declarative way to define routes:
4//!
5//! ```rust,ignore
6//! use kit::{routes, get, post, put, delete, group};
7//!
8//! routes! {
9//!     get("/", controllers::home::index).name("home"),
10//!     get("/users", controllers::user::index).name("users.index"),
11//!     post("/users", controllers::user::store).name("users.store"),
12//!     get("/protected", controllers::home::index).middleware(AuthMiddleware),
13//!
14//!     // Route groups with prefix and middleware
15//!     group!("/api", {
16//!         get("/users", controllers::api::user::index).name("api.users.index"),
17//!         post("/users", controllers::api::user::store).name("api.users.store"),
18//!     }).middleware(AuthMiddleware),
19//! }
20//! ```
21
22use crate::http::{Request, Response};
23use crate::middleware::{into_boxed, BoxedMiddleware, Middleware};
24use crate::routing::router::{register_route_name, BoxedHandler, Router};
25use std::future::Future;
26use std::sync::Arc;
27
28/// HTTP method for route definitions
29#[derive(Clone, Copy)]
30pub enum HttpMethod {
31    Get,
32    Post,
33    Put,
34    Delete,
35}
36
37/// Builder for route definitions that supports `.name()` and `.middleware()` chaining
38pub struct RouteDefBuilder<H> {
39    method: HttpMethod,
40    path: &'static str,
41    handler: H,
42    name: Option<&'static str>,
43    middlewares: Vec<BoxedMiddleware>,
44}
45
46impl<H, Fut> RouteDefBuilder<H>
47where
48    H: Fn(Request) -> Fut + Send + Sync + 'static,
49    Fut: Future<Output = Response> + Send + 'static,
50{
51    /// Create a new route definition builder
52    pub fn new(method: HttpMethod, path: &'static str, handler: H) -> Self {
53        Self {
54            method,
55            path,
56            handler,
57            name: None,
58            middlewares: Vec::new(),
59        }
60    }
61
62    /// Name this route for URL generation
63    pub fn name(mut self, name: &'static str) -> Self {
64        self.name = Some(name);
65        self
66    }
67
68    /// Add middleware to this route
69    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
70        self.middlewares.push(into_boxed(middleware));
71        self
72    }
73
74    /// Register this route definition with a router
75    pub fn register(self, router: Router) -> Router {
76        // First, register the route based on method
77        let builder = match self.method {
78            HttpMethod::Get => router.get(self.path, self.handler),
79            HttpMethod::Post => router.post(self.path, self.handler),
80            HttpMethod::Put => router.put(self.path, self.handler),
81            HttpMethod::Delete => router.delete(self.path, self.handler),
82        };
83
84        // Apply any middleware
85        let builder = self
86            .middlewares
87            .into_iter()
88            .fold(builder, |b, m| b.middleware_boxed(m));
89
90        // Apply name if present, otherwise convert to Router
91        if let Some(name) = self.name {
92            builder.name(name)
93        } else {
94            builder.into()
95        }
96    }
97}
98
99/// Create a GET route definition
100///
101/// # Example
102/// ```rust,ignore
103/// get("/users", controllers::user::index).name("users.index")
104/// ```
105pub fn get<H, Fut>(path: &'static str, handler: H) -> RouteDefBuilder<H>
106where
107    H: Fn(Request) -> Fut + Send + Sync + 'static,
108    Fut: Future<Output = Response> + Send + 'static,
109{
110    RouteDefBuilder::new(HttpMethod::Get, path, handler)
111}
112
113/// Create a POST route definition
114///
115/// # Example
116/// ```rust,ignore
117/// post("/users", controllers::user::store).name("users.store")
118/// ```
119pub fn post<H, Fut>(path: &'static str, handler: H) -> RouteDefBuilder<H>
120where
121    H: Fn(Request) -> Fut + Send + Sync + 'static,
122    Fut: Future<Output = Response> + Send + 'static,
123{
124    RouteDefBuilder::new(HttpMethod::Post, path, handler)
125}
126
127/// Create a PUT route definition
128///
129/// # Example
130/// ```rust,ignore
131/// put("/users/{id}", controllers::user::update).name("users.update")
132/// ```
133pub fn put<H, Fut>(path: &'static str, handler: H) -> RouteDefBuilder<H>
134where
135    H: Fn(Request) -> Fut + Send + Sync + 'static,
136    Fut: Future<Output = Response> + Send + 'static,
137{
138    RouteDefBuilder::new(HttpMethod::Put, path, handler)
139}
140
141/// Create a DELETE route definition
142///
143/// # Example
144/// ```rust,ignore
145/// delete("/users/{id}", controllers::user::destroy).name("users.destroy")
146/// ```
147pub fn delete<H, Fut>(path: &'static str, handler: H) -> RouteDefBuilder<H>
148where
149    H: Fn(Request) -> Fut + Send + Sync + 'static,
150    Fut: Future<Output = Response> + Send + 'static,
151{
152    RouteDefBuilder::new(HttpMethod::Delete, path, handler)
153}
154
155// ============================================================================
156// Route Grouping Support
157// ============================================================================
158
159/// A route stored within a group (type-erased handler)
160pub struct GroupRoute {
161    method: HttpMethod,
162    path: &'static str,
163    handler: Arc<BoxedHandler>,
164    name: Option<&'static str>,
165    middlewares: Vec<BoxedMiddleware>,
166}
167
168/// Group definition that collects routes and applies prefix/middleware
169///
170/// # Example
171///
172/// ```rust,ignore
173/// routes! {
174///     group!("/api", {
175///         get("/users", controllers::user::index).name("api.users"),
176///         post("/users", controllers::user::store),
177///     }).middleware(AuthMiddleware),
178/// }
179/// ```
180pub struct GroupDef {
181    prefix: &'static str,
182    routes: Vec<GroupRoute>,
183    group_middlewares: Vec<BoxedMiddleware>,
184}
185
186impl GroupDef {
187    /// Create a new route group with the given prefix
188    pub fn new(prefix: &'static str) -> Self {
189        Self {
190            prefix,
191            routes: Vec::new(),
192            group_middlewares: Vec::new(),
193        }
194    }
195
196    /// Add a route to this group
197    pub fn route<H, Fut>(mut self, route: RouteDefBuilder<H>) -> Self
198    where
199        H: Fn(Request) -> Fut + Send + Sync + 'static,
200        Fut: Future<Output = Response> + Send + 'static,
201    {
202        self.routes.push(route.into_group_route());
203        self
204    }
205
206    /// Add middleware to all routes in this group
207    ///
208    /// Middleware is applied in the order it's added.
209    ///
210    /// # Example
211    ///
212    /// ```rust,ignore
213    /// group!("/api", {
214    ///     get("/users", handler),
215    /// }).middleware(AuthMiddleware).middleware(RateLimitMiddleware)
216    /// ```
217    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
218        self.group_middlewares.push(into_boxed(middleware));
219        self
220    }
221
222    /// Register all routes in this group with the router
223    ///
224    /// This prepends the group prefix to each route path and applies
225    /// group middleware to all routes.
226    pub fn register(self, mut router: Router) -> Router {
227        for route in self.routes {
228            // Build full path with prefix
229            let full_path = format!("{}{}", self.prefix, route.path);
230            // We need to leak the string to get a 'static str for the router
231            let full_path: &'static str = Box::leak(full_path.into_boxed_str());
232
233            // Register the route with the router
234            match route.method {
235                HttpMethod::Get => {
236                    router.insert_get(full_path, route.handler);
237                }
238                HttpMethod::Post => {
239                    router.insert_post(full_path, route.handler);
240                }
241                HttpMethod::Put => {
242                    router.insert_put(full_path, route.handler);
243                }
244                HttpMethod::Delete => {
245                    router.insert_delete(full_path, route.handler);
246                }
247            }
248
249            // Register route name if present
250            if let Some(name) = route.name {
251                register_route_name(name, full_path);
252            }
253
254            // Apply group middleware first (outer), then route-specific middleware (inner)
255            for mw in &self.group_middlewares {
256                router.add_middleware(full_path, mw.clone());
257            }
258            for mw in route.middlewares {
259                router.add_middleware(full_path, mw);
260            }
261        }
262
263        router
264    }
265}
266
267impl<H, Fut> RouteDefBuilder<H>
268where
269    H: Fn(Request) -> Fut + Send + Sync + 'static,
270    Fut: Future<Output = Response> + Send + 'static,
271{
272    /// Convert this route definition to a type-erased GroupRoute
273    ///
274    /// This is used internally when adding routes to a group.
275    pub fn into_group_route(self) -> GroupRoute {
276        let handler = self.handler;
277        let boxed: BoxedHandler = Box::new(move |req| Box::pin(handler(req)));
278        GroupRoute {
279            method: self.method,
280            path: self.path,
281            handler: Arc::new(boxed),
282            name: self.name,
283            middlewares: self.middlewares,
284        }
285    }
286}
287
288/// Define a route group with a shared prefix
289///
290/// Routes within a group will have the prefix prepended to their paths.
291/// Middleware can be applied to the entire group using `.middleware()`.
292///
293/// # Example
294///
295/// ```rust,ignore
296/// use kit::{routes, get, post, group};
297///
298/// routes! {
299///     get("/", controllers::home::index),
300///
301///     // All routes in this group start with /api
302///     group!("/api", {
303///         get("/users", controllers::user::index),      // -> GET /api/users
304///         post("/users", controllers::user::store),     // -> POST /api/users
305///     }).middleware(AuthMiddleware),
306/// }
307/// ```
308#[macro_export]
309macro_rules! group {
310    ($prefix:expr, { $( $route:expr ),* $(,)? }) => {{
311        let mut group = $crate::GroupDef::new($prefix);
312        $(
313            group = group.route($route);
314        )*
315        group
316    }};
317}
318
319/// Define routes with a clean, Laravel-like syntax
320///
321/// This macro generates a `pub fn register() -> Router` function automatically.
322/// Place it at the top level of your `routes.rs` file.
323///
324/// # Example
325/// ```rust,ignore
326/// use kit::{routes, get, post, put, delete};
327/// use crate::controllers;
328/// use crate::middleware::AuthMiddleware;
329///
330/// routes! {
331///     get("/", controllers::home::index).name("home"),
332///     get("/users", controllers::user::index).name("users.index"),
333///     get("/users/{id}", controllers::user::show).name("users.show"),
334///     post("/users", controllers::user::store).name("users.store"),
335///     put("/users/{id}", controllers::user::update).name("users.update"),
336///     delete("/users/{id}", controllers::user::destroy).name("users.destroy"),
337///     get("/protected", controllers::home::index).middleware(AuthMiddleware),
338/// }
339/// ```
340#[macro_export]
341macro_rules! routes {
342    ( $( $route:expr ),* $(,)? ) => {
343        pub fn register() -> $crate::Router {
344            let mut router = $crate::Router::new();
345            $(
346                router = $route.register(router);
347            )*
348            router
349        }
350    };
351}