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}