Skip to main content

tako_rs_core/router/
registration.rs

1//! Route registration and HTTP-method builder shorthands.
2
3use std::sync::Arc;
4
5use http::Method;
6
7use super::Router;
8use crate::handler::BoxHandler;
9use crate::handler::Handler;
10use crate::route::Route;
11
12impl Router {
13  /// Registers a new route with the router.
14  ///
15  /// Associates an HTTP method and path pattern with a handler function. The path
16  /// can contain dynamic segments using curly braces (e.g., `/users/{id}`), which
17  /// are extracted as parameters during request processing.
18  ///
19  /// # Panics
20  ///
21  /// Panics if a route with the same method and path pattern is already registered.
22  ///
23  /// # Examples
24  ///
25  /// ```rust
26  /// use tako::{router::Router, Method, responder::Responder, types::Request};
27  ///
28  /// async fn get_user(_req: Request) -> impl Responder {
29  ///     "User details"
30  /// }
31  ///
32  /// async fn create_user(_req: Request) -> impl Responder {
33  ///     "User created"
34  /// }
35  ///
36  /// let mut router = Router::new();
37  /// router.route(Method::GET, "/users/{id}", get_user);
38  /// router.route(Method::POST, "/users", create_user);
39  /// router.route(Method::GET, "/health", |_req| async { "OK" });
40  /// ```
41  pub fn route<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
42  where
43    H: Handler<T> + Clone + 'static,
44  {
45    let final_path = self.apply_pending_prefix(path);
46    let route = Arc::new(Route::new(
47      final_path.clone(),
48      method.clone(),
49      BoxHandler::new::<H, T>(handler),
50      None,
51    ));
52
53    if let Err(err) = self
54      .inner
55      .get_or_default_mut(&method)
56      .insert(final_path, route.clone())
57    {
58      panic!("Failed to register route: {err}");
59    }
60
61    self
62      .routes
63      .get_or_default_mut(&method)
64      .push(Arc::downgrade(&route));
65
66    route
67  }
68
69  /// Returns `path` with the active `pending_prefix` (if any) prepended.
70  /// Cold path; only runs at registration time.
71  pub(crate) fn apply_pending_prefix(&self, path: &str) -> String {
72    match &self.pending_prefix {
73      None => path.to_string(),
74      Some(prefix) => {
75        let prefix = prefix.trim_end_matches('/');
76        if path.is_empty() || path == "/" {
77          if prefix.is_empty() {
78            "/".to_string()
79          } else {
80            prefix.to_string()
81          }
82        } else if path.starts_with('/') {
83          let mut s = String::with_capacity(prefix.len() + path.len());
84          s.push_str(prefix);
85          s.push_str(path);
86          s
87        } else {
88          let mut s = String::with_capacity(prefix.len() + 1 + path.len());
89          s.push_str(prefix);
90          s.push('/');
91          s.push_str(path);
92          s
93        }
94      }
95    }
96  }
97
98  /// Registers a `GET` route. Shorthand for [`Router::route`] with [`Method::GET`].
99  #[inline]
100  pub fn get<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
101  where
102    H: Handler<T> + Clone + 'static,
103  {
104    self.route(Method::GET, path, handler)
105  }
106
107  /// Registers a `POST` route. Shorthand for [`Router::route`] with [`Method::POST`].
108  #[inline]
109  pub fn post<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
110  where
111    H: Handler<T> + Clone + 'static,
112  {
113    self.route(Method::POST, path, handler)
114  }
115
116  /// Registers a `PUT` route. Shorthand for [`Router::route`] with [`Method::PUT`].
117  #[inline]
118  pub fn put<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
119  where
120    H: Handler<T> + Clone + 'static,
121  {
122    self.route(Method::PUT, path, handler)
123  }
124
125  /// Registers a `DELETE` route. Shorthand for [`Router::route`] with [`Method::DELETE`].
126  #[inline]
127  pub fn delete<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
128  where
129    H: Handler<T> + Clone + 'static,
130  {
131    self.route(Method::DELETE, path, handler)
132  }
133
134  /// Registers a `PATCH` route. Shorthand for [`Router::route`] with [`Method::PATCH`].
135  #[inline]
136  pub fn patch<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
137  where
138    H: Handler<T> + Clone + 'static,
139  {
140    self.route(Method::PATCH, path, handler)
141  }
142
143  /// Registers a `HEAD` route. Shorthand for [`Router::route`] with [`Method::HEAD`].
144  #[inline]
145  pub fn head<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
146  where
147    H: Handler<T> + Clone + 'static,
148  {
149    self.route(Method::HEAD, path, handler)
150  }
151
152  /// Registers an `OPTIONS` route. Shorthand for [`Router::route`] with [`Method::OPTIONS`].
153  #[inline]
154  pub fn options<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
155  where
156    H: Handler<T> + Clone + 'static,
157  {
158    self.route(Method::OPTIONS, path, handler)
159  }
160
161  /// Registers a route with trailing slash redirection enabled.
162  ///
163  /// When TSR is enabled, requests to paths with or without trailing slashes
164  /// are automatically redirected to the canonical version. This helps maintain
165  /// consistent URLs and prevents duplicate content issues.
166  ///
167  /// # Panics
168  ///
169  /// - Panics if called with the root path (`"/"`) since TSR is not applicable.
170  /// - Panics if a route with the same method and path pattern is already registered.
171  ///
172  /// # Examples
173  ///
174  /// ```rust
175  /// use tako::{router::Router, Method, responder::Responder, types::Request};
176  ///
177  /// async fn api_handler(_req: Request) -> impl Responder {
178  ///     "API endpoint"
179  /// }
180  ///
181  /// let mut router = Router::new();
182  /// // Both "/api" and "/api/" will redirect to the canonical form
183  /// router.route_with_tsr(Method::GET, "/api", api_handler);
184  /// ```
185  pub fn route_with_tsr<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
186  where
187    H: Handler<T> + Clone + 'static,
188  {
189    assert!(path != "/", "Cannot route with TSR for root path");
190
191    let final_path = self.apply_pending_prefix(path);
192    let route = Arc::new(Route::new(
193      final_path.clone(),
194      method.clone(),
195      BoxHandler::new::<H, T>(handler),
196      Some(true),
197    ));
198
199    if let Err(err) = self
200      .inner
201      .get_or_default_mut(&method)
202      .insert(final_path, route.clone())
203    {
204      panic!("Failed to register route: {err}");
205    }
206
207    self
208      .routes
209      .get_or_default_mut(&method)
210      .push(Arc::downgrade(&route));
211
212    route
213  }
214}