Skip to main content

tako_rs_core/router/
definition.rs

1//! The [`Router`] type definition, its fields, and constructors.
2
3use std::sync::Arc;
4use std::sync::Weak;
5use std::sync::atomic::AtomicBool;
6use std::time::Duration;
7
8use arc_swap::ArcSwap;
9
10use super::ErrorHandler;
11use super::method_map::MethodMap;
12use crate::handler::BoxHandler;
13#[cfg(feature = "plugins")]
14use crate::plugins::TakoPlugin;
15use crate::route::Route;
16use crate::router_state::RouterState;
17#[cfg(feature = "signals")]
18use crate::signals::SignalArbiter;
19use crate::types::BoxMiddleware;
20
21/// HTTP router for managing routes, middleware, and request dispatching.
22///
23/// The `Router` is the central component for routing HTTP requests to appropriate
24/// handlers. It supports dynamic path parameters, middleware chains, plugin integration,
25/// and global state management. Routes are matched based on HTTP method and path pattern,
26/// with support for trailing slash redirection and parameter extraction.
27///
28/// # Examples
29///
30/// ```rust
31/// use tako::{router::Router, Method, responder::Responder, types::Request};
32///
33/// async fn index(_req: Request) -> impl Responder {
34///     "Welcome to the home page!"
35/// }
36///
37/// async fn user_profile(_req: Request) -> impl Responder {
38///     "User profile page"
39/// }
40///
41/// let mut router = Router::new();
42/// router.route(Method::GET, "/", index);
43/// router.route(Method::GET, "/users/{id}", user_profile);
44/// router.state("app_name", "MyApp".to_string());
45/// ```
46#[doc(alias = "router")]
47pub struct Router {
48  /// Map of registered routes keyed by method (O(1) array lookup).
49  pub(crate) inner: MethodMap<matchit::Router<Arc<Route>>>,
50  /// An easy-to-iterate index of the same routes so we can access the `Arc<Route>` values.
51  ///
52  /// Holds `Weak<Route>` (not `Arc`) so an external holder of an `Arc<Route>`
53  /// returned from [`Router::route`] can release it without keeping the router
54  /// graph alive past its useful lifetime. All current code paths that store a
55  /// `Weak` here also store the matching `Arc` in `inner`, so upgrades always
56  /// succeed today; [`Router::compact_routes`] sweeps dangling weaks lazily so
57  /// any future API that removes from `inner` does not cause this index to
58  /// grow without bound.
59  pub(crate) routes: MethodMap<Vec<Weak<Route>>>,
60  /// Optional path prefix prepended to every `route()` call while it is set.
61  /// Used by [`Router::mount_all_into`] and [`Router::scope`] (see v2 roadmap).
62  /// Only consulted at registration time — zero cost on the dispatch hot path.
63  pub(crate) pending_prefix: Option<String>,
64  /// Global middleware chain applied to all routes.
65  pub(crate) middlewares: ArcSwap<Vec<BoxMiddleware>>,
66  /// Fast check: true when global middleware is registered (avoids `ArcSwap` load on hot path).
67  pub(crate) has_global_middleware: AtomicBool,
68  /// Optional fallback handler executed when no route matches.
69  pub(crate) fallback: Option<BoxHandler>,
70  /// Registered plugins for extending functionality.
71  #[cfg(feature = "plugins")]
72  pub(crate) plugins: Vec<Box<dyn TakoPlugin>>,
73  /// Flag to ensure plugins are initialized only once.
74  #[cfg(feature = "plugins")]
75  pub(crate) plugins_initialized: AtomicBool,
76  /// Signal arbiter for in-process event emission and handling.
77  #[cfg(feature = "signals")]
78  pub(crate) signals: SignalArbiter,
79  /// Default timeout for all routes.
80  pub(crate) timeout: Option<Duration>,
81  /// Fallback handler executed when a request times out.
82  pub(crate) timeout_fallback: Option<BoxHandler>,
83  /// Global error handler for 5xx responses.
84  pub(crate) error_handler: Option<ErrorHandler>,
85  /// Global error handler for 4xx responses (opt-in; runs after dispatch).
86  pub(crate) client_error_handler: Option<ErrorHandler>,
87  /// Per-router typed state populated via [`Router::with_state`].
88  /// `Arc` is shared with every dispatched request via the request extension
89  /// so the `State<T>` extractor can read instance-local values.
90  pub(crate) router_state: Arc<RouterState>,
91  /// Fast-path flag: when `false`, dispatch skips the per-request Arc clone +
92  /// extension insert that wires `router_state` into requests.
93  pub(crate) has_router_state: AtomicBool,
94}
95
96impl Default for Router {
97  #[inline]
98  fn default() -> Self {
99    Self::new()
100  }
101}
102
103impl Router {
104  /// Creates a new, empty router.
105  #[must_use]
106  pub fn new() -> Self {
107    let router = Self {
108      inner: MethodMap::new(),
109      routes: MethodMap::new(),
110      pending_prefix: None,
111      middlewares: ArcSwap::new(Arc::default()),
112      has_global_middleware: AtomicBool::new(false),
113      fallback: None,
114      #[cfg(feature = "plugins")]
115      plugins: Vec::new(),
116      #[cfg(feature = "plugins")]
117      plugins_initialized: AtomicBool::new(false),
118      #[cfg(feature = "signals")]
119      signals: SignalArbiter::new(),
120      timeout: None,
121      timeout_fallback: None,
122      error_handler: None,
123      client_error_handler: None,
124      router_state: Arc::new(RouterState::new()),
125      has_router_state: AtomicBool::new(false),
126    };
127
128    #[cfg(feature = "signals")]
129    {
130      // Atomic first-write: under concurrent `Router::new` calls the
131      // previous `get_state.is_none() → set_state` pair was TOCTOU and let
132      // two threads each install their own arbiter. `get_or_init_state`
133      // resolves both to the same `Arc<SignalArbiter>`.
134      let arbiter_clone = router.signals.clone();
135      let _ = crate::state::get_or_init_state::<SignalArbiter, _>(move || arbiter_clone);
136    }
137
138    router
139  }
140}