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}