tako/
router.rs

1//! HTTP request routing and dispatch functionality.
2//!
3//! This module provides the core `Router` struct that manages HTTP routes, middleware chains,
4//! and request dispatching. The router supports dynamic path parameters, middleware composition,
5//! plugin integration, and global state management. It handles matching incoming requests to
6//! registered routes and executing the appropriate handlers through middleware pipelines.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use tako::{router::Router, Method, responder::Responder, types::Request};
12//!
13//! async fn hello(_req: Request) -> impl Responder {
14//!     "Hello, World!"
15//! }
16//!
17//! async fn user_handler(_req: Request) -> impl Responder {
18//!     "User profile"
19//! }
20//!
21//! let mut router = Router::new();
22//! router.route(Method::GET, "/", hello);
23//! router.route(Method::GET, "/users/{id}", user_handler);
24//!
25//! // Add global middleware
26//! router.middleware(|req, next| async move {
27//!     println!("Processing request to: {}", req.uri());
28//!     next.run(req).await
29//! });
30//! ```
31
32use std::{
33  collections::HashMap,
34  sync::{Arc, Weak},
35};
36
37use dashmap::DashMap;
38use http::Method;
39use http::StatusCode;
40use parking_lot::RwLock;
41
42use crate::{
43  body::TakoBody,
44  extractors::params::PathParams,
45  handler::{BoxHandler, Handler},
46  middleware::Next,
47  responder::Responder,
48  route::Route,
49  state::set_state,
50  types::{BoxMiddleware, Request, Response},
51};
52
53#[cfg(feature = "signals")]
54use crate::signals::{Signal, SignalArbiter, ids};
55
56#[cfg(feature = "plugins")]
57use crate::plugins::TakoPlugin;
58
59#[cfg(feature = "plugins")]
60use std::sync::atomic::AtomicBool;
61
62/// HTTP router for managing routes, middleware, and request dispatching.
63///
64/// The `Router` is the central component for routing HTTP requests to appropriate
65/// handlers. It supports dynamic path parameters, middleware chains, plugin integration,
66/// and global state management. Routes are matched based on HTTP method and path pattern,
67/// with support for trailing slash redirection and parameter extraction.
68///
69/// # Examples
70///
71/// ```rust
72/// use tako::{router::Router, Method, responder::Responder, types::Request};
73///
74/// async fn index(_req: Request) -> impl Responder {
75///     "Welcome to the home page!"
76/// }
77///
78/// async fn user_profile(_req: Request) -> impl Responder {
79///     "User profile page"
80/// }
81///
82/// let mut router = Router::new();
83/// router.route(Method::GET, "/", index);
84/// router.route(Method::GET, "/users/{id}", user_profile);
85/// router.state("app_name", "MyApp".to_string());
86/// ```
87#[doc(alias = "router")]
88pub struct Router {
89  /// Map of registered routes keyed by method.
90  inner: DashMap<Method, matchit::Router<Arc<Route>>>,
91  /// An easy-to-iterate index of the same routes so we can access the `Arc<Route>` values
92  routes: DashMap<Method, Vec<Weak<Route>>>,
93  /// Global middleware chain applied to all routes.
94  pub(crate) middlewares: RwLock<Vec<BoxMiddleware>>,
95  /// Optional fallback handler executed when no route matches.
96  fallback: Option<BoxHandler>,
97  /// Registered plugins for extending functionality.
98  #[cfg(feature = "plugins")]
99  plugins: Vec<Box<dyn TakoPlugin>>,
100  /// Flag to ensure plugins are initialized only once.
101  #[cfg(feature = "plugins")]
102  plugins_initialized: AtomicBool,
103  /// Signal arbiter for in-process event emission and handling.
104  #[cfg(feature = "signals")]
105  signals: SignalArbiter,
106}
107
108impl Router {
109  /// Creates a new, empty router.
110  pub fn new() -> Self {
111    Self {
112      inner: DashMap::default(),
113      routes: DashMap::default(),
114      middlewares: RwLock::new(Vec::new()),
115      fallback: None,
116      #[cfg(feature = "plugins")]
117      plugins: Vec::new(),
118      #[cfg(feature = "plugins")]
119      plugins_initialized: AtomicBool::new(false),
120      #[cfg(feature = "signals")]
121      signals: SignalArbiter::new(),
122    }
123  }
124
125  /// Registers a new route with the router.
126  ///
127  /// Associates an HTTP method and path pattern with a handler function. The path
128  /// can contain dynamic segments using curly braces (e.g., `/users/{id}`), which
129  /// are extracted as parameters during request processing.
130  ///
131  /// # Examples
132  ///
133  /// ```rust
134  /// use tako::{router::Router, Method, responder::Responder, types::Request};
135  ///
136  /// async fn get_user(_req: Request) -> impl Responder {
137  ///     "User details"
138  /// }
139  ///
140  /// async fn create_user(_req: Request) -> impl Responder {
141  ///     "User created"
142  /// }
143  ///
144  /// let mut router = Router::new();
145  /// router.route(Method::GET, "/users/{id}", get_user);
146  /// router.route(Method::POST, "/users", create_user);
147  /// router.route(Method::GET, "/health", |_req| async { "OK" });
148  /// ```
149  pub fn route<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
150  where
151    H: Handler<T> + Clone + 'static,
152  {
153    let route = Arc::new(Route::new(
154      path.to_string(),
155      method.clone(),
156      BoxHandler::new::<H, T>(handler),
157      None,
158    ));
159
160    let mut method_router = self.inner.entry(method.clone()).or_default();
161
162    if let Err(err) = method_router.insert(path.to_string(), route.clone()) {
163      panic!("Failed to register route: {err}");
164    }
165
166    self
167      .routes
168      .entry(method)
169      .or_default()
170      .push(Arc::downgrade(&route));
171
172    route
173  }
174
175  /// Registers a route with trailing slash redirection enabled.
176  ///
177  /// When TSR is enabled, requests to paths with or without trailing slashes
178  /// are automatically redirected to the canonical version. This helps maintain
179  /// consistent URLs and prevents duplicate content issues.
180  ///
181  /// # Panics
182  ///
183  /// Panics if called with the root path ("/") since TSR is not applicable.
184  ///
185  /// # Examples
186  ///
187  /// ```rust
188  /// use tako::{router::Router, Method, responder::Responder, types::Request};
189  ///
190  /// async fn api_handler(_req: Request) -> impl Responder {
191  ///     "API endpoint"
192  /// }
193  ///
194  /// let mut router = Router::new();
195  /// // Both "/api" and "/api/" will redirect to the canonical form
196  /// router.route_with_tsr(Method::GET, "/api", api_handler);
197  /// ```
198  pub fn route_with_tsr<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
199  where
200    H: Handler<T> + Clone + 'static,
201  {
202    if path == "/" {
203      panic!("Cannot route with TSR for root path");
204    }
205
206    let route = Arc::new(Route::new(
207      path.to_string(),
208      method.clone(),
209      BoxHandler::new::<H, T>(handler),
210      Some(true),
211    ));
212
213    let mut method_router = self.inner.entry(method.clone()).or_default();
214
215    if let Err(err) = method_router.insert(path.to_string(), route.clone()) {
216      panic!("Failed to register route: {err}");
217    }
218
219    self
220      .routes
221      .entry(method)
222      .or_default()
223      .push(Arc::downgrade(&route));
224
225    route
226  }
227
228  /// Executes the given endpoint through the global middleware chain.
229  ///
230  /// This helper is used for cases like TSR redirects and default 404 responses,
231  /// ensuring that router-level middleware (e.g., CORS) always runs.
232  async fn run_with_global_middlewares_for_endpoint(
233    &self,
234    req: Request,
235    endpoint: BoxHandler,
236  ) -> Response {
237    let g_mws = self.middlewares.read().clone();
238    let next = Next {
239      middlewares: Arc::new(g_mws),
240      endpoint: Arc::new(endpoint),
241    };
242
243    next.run(req).await
244  }
245
246  /// Dispatches an incoming request to the appropriate route handler.
247  pub async fn dispatch(&self, mut req: Request) -> Response {
248    let method = req.method().clone();
249    let path = req.uri().path().to_string();
250
251    if let Some(method_router) = self.inner.get(&method)
252      && let Ok(matched) = method_router.at(&path)
253    {
254      let route = matched.value;
255
256      // Protocol guard: early-return if request version does not satisfy route guard
257      if let Some(res) = Self::enforce_protocol_guard(route, &req) {
258        return res;
259      }
260
261      #[cfg(feature = "signals")]
262      let route_signals = route.signal_arbiter();
263
264      // Initialize route-level plugins on first request
265      #[cfg(feature = "plugins")]
266      route.setup_plugins_once();
267
268      if !matched.params.iter().collect::<Vec<_>>().is_empty() {
269        let mut params = HashMap::new();
270        for (k, v) in matched.params.iter() {
271          params.insert(k.to_string(), v.to_string());
272        }
273        req.extensions_mut().insert(PathParams(params));
274      }
275      let g_mws = self.middlewares.read().clone();
276      let r_mws = route.middlewares.read().clone();
277      let mut chain = Vec::new();
278      chain.extend(g_mws.into_iter());
279      chain.extend(r_mws.into_iter());
280
281      let next = Next {
282        middlewares: Arc::new(chain),
283        endpoint: Arc::new(route.handler.clone()),
284      };
285
286      #[cfg(feature = "signals")]
287      {
288        let method_str = method.to_string();
289        let path_str = path.clone();
290
291        let mut start_meta = HashMap::new();
292        start_meta.insert("method".to_string(), method_str.clone());
293        start_meta.insert("path".to_string(), path_str.clone());
294        route_signals
295          .emit(Signal::with_metadata(
296            ids::ROUTE_REQUEST_STARTED,
297            start_meta,
298          ))
299          .await;
300
301        let response = next.run(req).await;
302
303        let mut done_meta = HashMap::new();
304        done_meta.insert("method".to_string(), method_str);
305        done_meta.insert("path".to_string(), path_str);
306        done_meta.insert("status".to_string(), response.status().as_u16().to_string());
307        route_signals
308          .emit(Signal::with_metadata(
309            ids::ROUTE_REQUEST_COMPLETED,
310            done_meta,
311          ))
312          .await;
313
314        return response;
315      }
316
317      #[cfg(not(feature = "signals"))]
318      {
319        return next.run(req).await;
320      }
321    }
322
323    let tsr_path = if path.ends_with('/') {
324      path.trim_end_matches('/').to_string()
325    } else {
326      format!("{path}/")
327    };
328
329    if let Some(method_router) = self.inner.get(&method)
330      && let Ok(matched) = method_router.at(&tsr_path)
331      && matched.value.tsr
332    {
333      let handler = move |_req: Request| async move {
334        http::Response::builder()
335          .status(StatusCode::TEMPORARY_REDIRECT)
336          .header("Location", tsr_path.clone())
337          .body(TakoBody::empty())
338          .unwrap()
339      };
340
341      return self
342        .run_with_global_middlewares_for_endpoint(req, BoxHandler::new::<_, (Request,)>(handler))
343        .await;
344    }
345
346    // No match: use fallback handler if configured
347    if let Some(handler) = &self.fallback {
348      return self
349        .run_with_global_middlewares_for_endpoint(req, handler.clone())
350        .await;
351    }
352
353    // No fallback: run global middlewares (if any) around a default 404 response
354    let handler = |_req: Request| async {
355      http::Response::builder()
356        .status(StatusCode::NOT_FOUND)
357        .body(TakoBody::empty())
358        .unwrap()
359    };
360
361    self
362      .run_with_global_middlewares_for_endpoint(req, BoxHandler::new::<_, (Request,)>(handler))
363      .await
364  }
365
366  /// Adds a value to the global type-based state accessible by all handlers.
367  ///
368  /// Global state allows sharing data across different routes and middleware.
369  /// Values are stored by their concrete type and retrieved via the
370  /// [`State`](crate::extractors::state::State) extractor or with
371  /// [`crate::state::get_state`].
372  ///
373  /// # Examples
374  ///
375  /// ```rust
376  /// use tako::router::Router;
377  ///
378  /// #[derive(Clone)]
379  /// struct AppConfig { database_url: String, api_key: String }
380  ///
381  /// let mut router = Router::new();
382  /// router.state(AppConfig {
383  ///     database_url: "postgresql://localhost/mydb".to_string(),
384  ///     api_key: "secret-key".to_string(),
385  /// });
386  /// // You can also store simple types by type:
387  /// router.state::<String>("1.0.0".to_string());
388  /// ```
389  pub fn state<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
390    set_state(value);
391  }
392
393  #[cfg(feature = "signals")]
394  /// Returns a reference to the signal arbiter.
395  pub fn signals(&self) -> &SignalArbiter {
396    &self.signals
397  }
398
399  #[cfg(feature = "signals")]
400  /// Returns a clone of the signal arbiter, useful for sharing through state.
401  pub fn signal_arbiter(&self) -> SignalArbiter {
402    self.signals.clone()
403  }
404
405  #[cfg(feature = "signals")]
406  /// Registers a handler for a named signal on this router's arbiter.
407  pub fn on_signal<F, Fut>(&self, id: impl Into<String>, handler: F)
408  where
409    F: Fn(Signal) -> Fut + Send + Sync + 'static,
410    Fut: std::future::Future<Output = ()> + Send + 'static,
411  {
412    self.signals.on(id, handler);
413  }
414
415  #[cfg(feature = "signals")]
416  /// Emits a signal through this router's arbiter.
417  pub async fn emit_signal(&self, signal: Signal) {
418    self.signals.emit(signal).await;
419  }
420
421  /// Adds global middleware to the router.
422  ///
423  /// Global middleware is executed for all routes in the order it was added,
424  /// before any route-specific middleware. Middleware can modify requests,
425  /// generate responses, or perform side effects like logging or authentication.
426  ///
427  /// # Examples
428  ///
429  /// ```rust
430  /// use tako::{router::Router, middleware::Next, types::Request};
431  ///
432  /// let mut router = Router::new();
433  ///
434  /// // Logging middleware
435  /// router.middleware(|req, next| async move {
436  ///     println!("Request: {} {}", req.method(), req.uri());
437  ///     let response = next.run(req).await;
438  ///     println!("Response: {}", response.status());
439  ///     response
440  /// });
441  ///
442  /// // Authentication middleware
443  /// router.middleware(|req, next| async move {
444  ///     if req.headers().contains_key("authorization") {
445  ///         next.run(req).await
446  ///     } else {
447  ///         "Unauthorized".into_response()
448  ///     }
449  /// });
450  /// ```
451  pub fn middleware<F, Fut, R>(&self, f: F) -> &Self
452  where
453    F: Fn(Request, Next) -> Fut + Clone + Send + Sync + 'static,
454    Fut: std::future::Future<Output = R> + Send + 'static,
455    R: Responder + Send + 'static,
456  {
457    let mw: BoxMiddleware = Arc::new(move |req, next| {
458      let fut = f(req, next);
459      Box::pin(async move { fut.await.into_response() })
460    });
461
462    self.middlewares.write().push(mw);
463    self
464  }
465
466  /// Sets a fallback handler that will be executed when no route matches.
467  ///
468  /// The fallback runs after global middlewares and can be used to implement
469  /// custom 404 pages, catch-all logic, or method-independent handlers.
470  ///
471  /// # Examples
472  ///
473  /// ```rust
474  /// use tako::{router::Router, Method, responder::Responder, types::Request};
475  ///
476  /// async fn not_found(_req: Request) -> impl Responder { "Not Found" }
477  ///
478  /// let mut router = Router::new();
479  /// router.route(Method::GET, "/", |_req| async { "Hello" });
480  /// router.fallback(not_found);
481  /// ```
482  pub fn fallback<F, Fut, R>(&mut self, handler: F) -> &mut Self
483  where
484    F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
485    Fut: std::future::Future<Output = R> + Send + 'static,
486    R: Responder + Send + 'static,
487  {
488    // Use the Request-arg handler impl to box the fallback
489    self.fallback = Some(BoxHandler::new::<F, (Request,)>(handler));
490    self
491  }
492
493  /// Sets a fallback handler that supports extractors (like `Path`, `Query`, etc.).
494  ///
495  /// Use this when your fallback needs to parse request data via extractors. If you
496  /// only need access to the raw `Request`, prefer `fallback` for simpler type inference.
497  ///
498  /// # Examples
499  ///
500  /// ```rust
501  /// use tako::{router::Router, responder::Responder, extractors::{path::Path, query::Query}};
502  ///
503  /// #[derive(serde::Deserialize)]
504  /// struct Q { q: Option<String> }
505  ///
506  /// async fn fallback_with_q(Path(_p): Path<String>, Query(_q): Query<Q>) -> impl Responder {
507  ///     "Not Found"
508  /// }
509  ///
510  /// let mut router = Router::new();
511  /// router.fallback_with_extractors(fallback_with_q);
512  /// ```
513  pub fn fallback_with_extractors<H, T>(&mut self, handler: H) -> &mut Self
514  where
515    H: Handler<T> + Clone + 'static,
516  {
517    self.fallback = Some(BoxHandler::new::<H, T>(handler));
518    self
519  }
520
521  /// Registers a plugin with the router.
522  ///
523  /// Plugins extend the router's functionality by providing additional features
524  /// like compression, CORS handling, rate limiting, or custom behavior. Plugins
525  /// are initialized once when the server starts.
526  ///
527  /// # Examples
528  ///
529  /// ```rust
530  /// # #[cfg(feature = "plugins")]
531  /// use tako::{router::Router, plugins::TakoPlugin};
532  /// # #[cfg(feature = "plugins")]
533  /// use anyhow::Result;
534  ///
535  /// # #[cfg(feature = "plugins")]
536  /// struct LoggingPlugin;
537  ///
538  /// # #[cfg(feature = "plugins")]
539  /// impl TakoPlugin for LoggingPlugin {
540  ///     fn name(&self) -> &'static str {
541  ///         "logging"
542  ///     }
543  ///
544  ///     fn setup(&self, _router: &Router) -> Result<()> {
545  ///         println!("Logging plugin initialized");
546  ///         Ok(())
547  ///     }
548  /// }
549  ///
550  /// # #[cfg(feature = "plugins")]
551  /// # fn example() {
552  /// let mut router = Router::new();
553  /// router.plugin(LoggingPlugin);
554  /// # }
555  /// ```
556  #[cfg(feature = "plugins")]
557  #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
558  pub fn plugin<P>(&mut self, plugin: P) -> &mut Self
559  where
560    P: TakoPlugin + Clone + Send + Sync + 'static,
561  {
562    self.plugins.push(Box::new(plugin));
563    self
564  }
565
566  /// Returns references to all registered plugins.
567  #[cfg(feature = "plugins")]
568  #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
569  pub(crate) fn plugins(&self) -> Vec<&dyn TakoPlugin> {
570    self.plugins.iter().map(|plugin| plugin.as_ref()).collect()
571  }
572
573  /// Initializes all registered plugins exactly once.
574  #[cfg(feature = "plugins")]
575  #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
576  pub(crate) fn setup_plugins_once(&self) {
577    use std::sync::atomic::Ordering;
578
579    if !self.plugins_initialized.swap(true, Ordering::SeqCst) {
580      for plugin in self.plugins() {
581        let _ = plugin.setup(self);
582      }
583    }
584  }
585
586  /// Merges another router into this router.
587  ///
588  /// This method combines routes and middleware from another router into the
589  /// current one. Routes are copied over, and the other router's global middleware
590  /// is prepended to each merged route's middleware chain.
591  ///
592  /// # Examples
593  ///
594  /// ```rust
595  /// use tako::{router::Router, Method, responder::Responder, types::Request};
596  ///
597  /// async fn api_handler(_req: Request) -> impl Responder {
598  ///     "API response"
599  /// }
600  ///
601  /// async fn web_handler(_req: Request) -> impl Responder {
602  ///     "Web response"
603  /// }
604  ///
605  /// // Create API router
606  /// let mut api_router = Router::new();
607  /// api_router.route(Method::GET, "/users", api_handler);
608  /// api_router.middleware(|req, next| async move {
609  ///     println!("API middleware");
610  ///     next.run(req).await
611  /// });
612  ///
613  /// // Create main router and merge API router
614  /// let mut main_router = Router::new();
615  /// main_router.route(Method::GET, "/", web_handler);
616  /// main_router.merge(api_router);
617  /// ```
618  pub fn merge(&mut self, other: Router) {
619    let upstream_globals = other.middlewares.read().clone();
620
621    for (method, weak_vec) in other.routes.into_iter() {
622      let mut target_router = self.inner.entry(method.clone()).or_default();
623
624      for weak in weak_vec {
625        if let Some(route) = weak.upgrade() {
626          let mut rmw = route.middlewares.write();
627          for mw in upstream_globals.iter().rev() {
628            rmw.push_front(mw.clone());
629          }
630
631          let _ = target_router.insert(route.path.clone(), route.clone());
632
633          self
634            .routes
635            .entry(method.clone())
636            .or_default()
637            .push(Arc::downgrade(&route));
638        }
639      }
640    }
641
642    #[cfg(feature = "signals")]
643    self.signals.merge_from(&other.signals);
644  }
645
646  /// Ensures the request HTTP version satisfies the route's configured protocol guard.
647  /// Returns `Some(Response)` with 505 HTTP Version Not Supported when the request
648  /// doesn't match the guard, otherwise returns `None` to continue dispatch.
649  fn enforce_protocol_guard(route: &Route, req: &Request) -> Option<Response> {
650    if let Some(guard) = route.protocol_guard()
651      && guard != req.version()
652    {
653      return Some(
654        http::Response::builder()
655          .status(StatusCode::HTTP_VERSION_NOT_SUPPORTED)
656          .body(TakoBody::empty())
657          .unwrap(),
658      );
659    }
660    None
661  }
662}