tako_rs_core/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::sync::Arc;
33use std::sync::Weak;
34use std::sync::atomic::AtomicBool;
35use std::sync::atomic::Ordering;
36use std::time::Duration;
37
38use arc_swap::ArcSwap;
39use http::Method;
40use http::StatusCode;
41use smallvec::SmallVec;
42
43use crate::body::TakoBody;
44use crate::extractors::params::PathParams;
45use crate::handler::BoxHandler;
46use crate::handler::Handler;
47use crate::middleware::Next;
48#[cfg(feature = "plugins")]
49use crate::plugins::TakoPlugin;
50use crate::responder::Responder;
51use crate::route::Route;
52use crate::router_state::RouterState;
53#[cfg(feature = "signals")]
54use crate::signals::Signal;
55#[cfg(feature = "signals")]
56use crate::signals::SignalArbiter;
57#[cfg(feature = "signals")]
58use crate::signals::ids;
59use crate::state::set_state;
60use crate::types::BoxMiddleware;
61use crate::types::Request;
62use crate::types::Response;
63
64/// Builds an empty-body response with the given status code without going
65/// through `http::response::Builder`. The builder API returns `Result` to
66/// surface invalid header values, but for the router's hot-path 404 / 405 /
67/// 408 / 505 responses we have no headers to fail on, so the result is
68/// statically infallible. This helper avoids `.expect("valid …")` calls in
69/// the dispatch path.
70#[inline]
71fn empty_status_response(status: StatusCode) -> Response {
72 let mut resp = http::Response::new(TakoBody::empty());
73 *resp.status_mut() = status;
74 resp
75}
76
77/// Type alias for a global error handler function.
78///
79/// Called when a response has a server error status (5xx). Receives the original
80/// response and can transform it (e.g., to return JSON errors instead of plain text).
81pub type ErrorHandler = Arc<dyn Fn(Response) -> Response + Send + Sync + 'static>;
82
83/// HTTP router for managing routes, middleware, and request dispatching.
84///
85/// The `Router` is the central component for routing HTTP requests to appropriate
86/// handlers. It supports dynamic path parameters, middleware chains, plugin integration,
87/// and global state management. Routes are matched based on HTTP method and path pattern,
88/// with support for trailing slash redirection and parameter extraction.
89///
90/// # Examples
91///
92/// ```rust
93/// use tako::{router::Router, Method, responder::Responder, types::Request};
94///
95/// async fn index(_req: Request) -> impl Responder {
96/// "Welcome to the home page!"
97/// }
98///
99/// async fn user_profile(_req: Request) -> impl Responder {
100/// "User profile page"
101/// }
102///
103/// let mut router = Router::new();
104/// router.route(Method::GET, "/", index);
105/// router.route(Method::GET, "/users/{id}", user_profile);
106/// router.state("app_name", "MyApp".to_string());
107/// ```
108#[doc(alias = "router")]
109pub struct Router {
110 /// Map of registered routes keyed by method (O(1) array lookup).
111 inner: MethodMap<matchit::Router<Arc<Route>>>,
112 /// An easy-to-iterate index of the same routes so we can access the `Arc<Route>` values.
113 ///
114 /// Holds `Weak<Route>` (not `Arc`) so an external holder of an `Arc<Route>`
115 /// returned from [`Router::route`] can release it without keeping the router
116 /// graph alive past its useful lifetime. All current code paths that store a
117 /// `Weak` here also store the matching `Arc` in `inner`, so upgrades always
118 /// succeed today; [`Router::compact_routes`] sweeps dangling weaks lazily so
119 /// any future API that removes from `inner` does not cause this index to
120 /// grow without bound.
121 routes: MethodMap<Vec<Weak<Route>>>,
122 /// Optional path prefix prepended to every `route()` call while it is set.
123 /// Used by [`Router::mount_all_into`] and [`Router::scope`] (see v2 roadmap).
124 /// Only consulted at registration time — zero cost on the dispatch hot path.
125 pending_prefix: Option<String>,
126 /// Global middleware chain applied to all routes.
127 pub(crate) middlewares: ArcSwap<Vec<BoxMiddleware>>,
128 /// Fast check: true when global middleware is registered (avoids `ArcSwap` load on hot path).
129 has_global_middleware: AtomicBool,
130 /// Optional fallback handler executed when no route matches.
131 fallback: Option<BoxHandler>,
132 /// Registered plugins for extending functionality.
133 #[cfg(feature = "plugins")]
134 plugins: Vec<Box<dyn TakoPlugin>>,
135 /// Flag to ensure plugins are initialized only once.
136 #[cfg(feature = "plugins")]
137 plugins_initialized: AtomicBool,
138 /// Signal arbiter for in-process event emission and handling.
139 #[cfg(feature = "signals")]
140 signals: SignalArbiter,
141 /// Default timeout for all routes.
142 pub(crate) timeout: Option<Duration>,
143 /// Fallback handler executed when a request times out.
144 timeout_fallback: Option<BoxHandler>,
145 /// Global error handler for 5xx responses.
146 error_handler: Option<ErrorHandler>,
147 /// Global error handler for 4xx responses (opt-in; runs after dispatch).
148 client_error_handler: Option<ErrorHandler>,
149 /// Per-router typed state populated via [`Router::with_state`].
150 /// `Arc` is shared with every dispatched request via the request extension
151 /// so the `State<T>` extractor can read instance-local values.
152 router_state: Arc<RouterState>,
153 /// Fast-path flag: when `false`, dispatch skips the per-request Arc clone +
154 /// extension insert that wires `router_state` into requests.
155 has_router_state: AtomicBool,
156}
157
158impl Default for Router {
159 #[inline]
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl Router {
166 /// Creates a new, empty router.
167 #[must_use]
168 pub fn new() -> Self {
169 let router = Self {
170 inner: MethodMap::new(),
171 routes: MethodMap::new(),
172 pending_prefix: None,
173 middlewares: ArcSwap::new(Arc::default()),
174 has_global_middleware: AtomicBool::new(false),
175 fallback: None,
176 #[cfg(feature = "plugins")]
177 plugins: Vec::new(),
178 #[cfg(feature = "plugins")]
179 plugins_initialized: AtomicBool::new(false),
180 #[cfg(feature = "signals")]
181 signals: SignalArbiter::new(),
182 timeout: None,
183 timeout_fallback: None,
184 error_handler: None,
185 client_error_handler: None,
186 router_state: Arc::new(RouterState::new()),
187 has_router_state: AtomicBool::new(false),
188 };
189
190 #[cfg(feature = "signals")]
191 {
192 // Atomic first-write: under concurrent `Router::new` calls the
193 // previous `get_state.is_none() → set_state` pair was TOCTOU and let
194 // two threads each install their own arbiter. `get_or_init_state`
195 // resolves both to the same `Arc<SignalArbiter>`.
196 let arbiter_clone = router.signals.clone();
197 let _ = crate::state::get_or_init_state::<SignalArbiter, _>(move || arbiter_clone);
198 }
199
200 router
201 }
202
203 /// Registers a new route with the router.
204 ///
205 /// Associates an HTTP method and path pattern with a handler function. The path
206 /// can contain dynamic segments using curly braces (e.g., `/users/{id}`), which
207 /// are extracted as parameters during request processing.
208 ///
209 /// # Panics
210 ///
211 /// Panics if a route with the same method and path pattern is already registered.
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// use tako::{router::Router, Method, responder::Responder, types::Request};
217 ///
218 /// async fn get_user(_req: Request) -> impl Responder {
219 /// "User details"
220 /// }
221 ///
222 /// async fn create_user(_req: Request) -> impl Responder {
223 /// "User created"
224 /// }
225 ///
226 /// let mut router = Router::new();
227 /// router.route(Method::GET, "/users/{id}", get_user);
228 /// router.route(Method::POST, "/users", create_user);
229 /// router.route(Method::GET, "/health", |_req| async { "OK" });
230 /// ```
231 pub fn route<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
232 where
233 H: Handler<T> + Clone + 'static,
234 {
235 let final_path = self.apply_pending_prefix(path);
236 let route = Arc::new(Route::new(
237 final_path.clone(),
238 method.clone(),
239 BoxHandler::new::<H, T>(handler),
240 None,
241 ));
242
243 if let Err(err) = self
244 .inner
245 .get_or_default_mut(&method)
246 .insert(final_path, route.clone())
247 {
248 panic!("Failed to register route: {err}");
249 }
250
251 self
252 .routes
253 .get_or_default_mut(&method)
254 .push(Arc::downgrade(&route));
255
256 route
257 }
258
259 /// Returns `path` with the active `pending_prefix` (if any) prepended.
260 /// Cold path; only runs at registration time.
261 fn apply_pending_prefix(&self, path: &str) -> String {
262 match &self.pending_prefix {
263 None => path.to_string(),
264 Some(prefix) => {
265 let prefix = prefix.trim_end_matches('/');
266 if path.is_empty() || path == "/" {
267 if prefix.is_empty() {
268 "/".to_string()
269 } else {
270 prefix.to_string()
271 }
272 } else if path.starts_with('/') {
273 let mut s = String::with_capacity(prefix.len() + path.len());
274 s.push_str(prefix);
275 s.push_str(path);
276 s
277 } else {
278 let mut s = String::with_capacity(prefix.len() + 1 + path.len());
279 s.push_str(prefix);
280 s.push('/');
281 s.push_str(path);
282 s
283 }
284 }
285 }
286 }
287
288 /// Registers a `GET` route. Shorthand for [`Router::route`] with [`Method::GET`].
289 #[inline]
290 pub fn get<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
291 where
292 H: Handler<T> + Clone + 'static,
293 {
294 self.route(Method::GET, path, handler)
295 }
296
297 /// Registers a `POST` route. Shorthand for [`Router::route`] with [`Method::POST`].
298 #[inline]
299 pub fn post<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
300 where
301 H: Handler<T> + Clone + 'static,
302 {
303 self.route(Method::POST, path, handler)
304 }
305
306 /// Registers a `PUT` route. Shorthand for [`Router::route`] with [`Method::PUT`].
307 #[inline]
308 pub fn put<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
309 where
310 H: Handler<T> + Clone + 'static,
311 {
312 self.route(Method::PUT, path, handler)
313 }
314
315 /// Registers a `DELETE` route. Shorthand for [`Router::route`] with [`Method::DELETE`].
316 #[inline]
317 pub fn delete<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
318 where
319 H: Handler<T> + Clone + 'static,
320 {
321 self.route(Method::DELETE, path, handler)
322 }
323
324 /// Registers a `PATCH` route. Shorthand for [`Router::route`] with [`Method::PATCH`].
325 #[inline]
326 pub fn patch<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
327 where
328 H: Handler<T> + Clone + 'static,
329 {
330 self.route(Method::PATCH, path, handler)
331 }
332
333 /// Registers a `HEAD` route. Shorthand for [`Router::route`] with [`Method::HEAD`].
334 #[inline]
335 pub fn head<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
336 where
337 H: Handler<T> + Clone + 'static,
338 {
339 self.route(Method::HEAD, path, handler)
340 }
341
342 /// Registers an `OPTIONS` route. Shorthand for [`Router::route`] with [`Method::OPTIONS`].
343 #[inline]
344 pub fn options<H, T>(&mut self, path: &str, handler: H) -> Arc<Route>
345 where
346 H: Handler<T> + Clone + 'static,
347 {
348 self.route(Method::OPTIONS, path, handler)
349 }
350
351 /// Registers every route declared via the `#[tako::route]` / `#[tako::get]`
352 /// (and friends) attribute macros into this router.
353 ///
354 /// Each macro contributes a thunk into the global [`TAKO_ROUTES`] slice at
355 /// link time; this method walks the slice and invokes each thunk against
356 /// `self`, which calls [`Router::route`] under the hood. Routes are
357 /// registered in the order the linker emits them — typically the order they
358 /// appear within a translation unit, but unspecified across crates. If two
359 /// thunks register the same `(method, path)` pair, the second call will
360 /// panic, matching the behavior of [`Router::route`].
361 ///
362 /// # Why `linkme` and not explicit registration
363 ///
364 /// We keep the `linkme` distributed-slice strategy on purpose. The
365 /// alternative — an explicit `register_routes!(my_crate::routes)` invocation
366 /// per crate — was considered and rejected because:
367 ///
368 /// * Adding a handler would require touching three places (the handler
369 /// itself, the per-crate registration list, and the call site that
370 /// imports it) instead of one. The macro authoring story is the main
371 /// reason teams pick attribute routing in the first place.
372 /// * Cross-crate path collisions panic at startup either way; explicit
373 /// registration does not buy any extra safety.
374 /// * Link-order non-determinism only matters when two routes share a
375 /// `(method, path)` pair — that is already a hard failure and a CI test
376 /// catches it deterministically.
377 /// * Prefix grouping is already covered by [`Router::mount_all_into`], so
378 /// "I want all my routes under `/api`" does not require explicit
379 /// registration.
380 ///
381 /// Callers that need stable, deterministic ordering should call
382 /// [`Router::route`] directly.
383 ///
384 /// # Examples
385 ///
386 /// ```ignore
387 /// use tako::{get, router::Router};
388 ///
389 /// #[get("/health")]
390 /// async fn health() -> impl tako::responder::Responder { "ok" }
391 ///
392 /// let mut router = Router::new();
393 /// router.mount_all();
394 /// ```
395 pub fn mount_all(&mut self) -> &mut Self {
396 for register in TAKO_ROUTES {
397 register(self);
398 }
399 self
400 }
401
402 /// Like [`Router::mount_all`] but registers every macro-declared route under
403 /// the given path prefix. The prefix is normalized (trailing `/` stripped),
404 /// then prepended to each registered path. Useful when you want, e.g., all
405 /// `#[get("/users")]` declarations to live under `/api`.
406 ///
407 /// Ordering across crates remains the linker's choice (see
408 /// [`Router::mount_all`] for details).
409 ///
410 /// # Examples
411 ///
412 /// ```ignore
413 /// let mut router = Router::new();
414 /// router.mount_all_into("/api"); // /users → /api/users, /health → /api/health
415 /// ```
416 pub fn mount_all_into(&mut self, prefix: &str) -> &mut Self {
417 let saved = self.pending_prefix.take();
418 self.pending_prefix = Some(prefix.to_string());
419 // Same panic-restore guard as `scope`: a route conflict from any
420 // registered `#[tako_route]` macro now resets `pending_prefix`.
421 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
422 for register in TAKO_ROUTES {
423 register(self);
424 }
425 }));
426 self.pending_prefix = saved;
427 if let Err(payload) = result {
428 std::panic::resume_unwind(payload);
429 }
430 self
431 }
432
433 /// Registers a group of routes under a shared path prefix.
434 ///
435 /// The closure receives `self` with the prefix active, so any `route()` /
436 /// `get()` / `post()` etc. calls inside register the routes with the prefix
437 /// prepended. Prefixes nest: a `scope("/v1", |r| r.scope("/users", …))`
438 /// produces routes under `/v1/users`. Cold path; no dispatch impact.
439 ///
440 /// # Examples
441 ///
442 /// ```rust
443 /// use tako::router::Router;
444 /// use tako::responder::Responder;
445 ///
446 /// async fn list_users() -> impl Responder { "users" }
447 /// async fn create_user() -> impl Responder { "created" }
448 ///
449 /// let mut router = Router::new();
450 /// router.scope("/api/v1", |r| {
451 /// r.get("/users", list_users);
452 /// r.post("/users", create_user);
453 /// });
454 /// ```
455 pub fn scope<F>(&mut self, prefix: &str, build: F) -> &mut Self
456 where
457 F: FnOnce(&mut Router),
458 {
459 let saved = self.pending_prefix.take();
460 let new_prefix = match &saved {
461 Some(parent) => {
462 let parent = parent.trim_end_matches('/');
463 if prefix.starts_with('/') {
464 format!("{parent}{prefix}")
465 } else {
466 format!("{parent}/{prefix}")
467 }
468 }
469 None => prefix.to_string(),
470 };
471 self.pending_prefix = Some(new_prefix);
472 // Panic-safe restore of `pending_prefix`. A route-conflict panic in the
473 // user-supplied `build` closure used to leave the temporary nested
474 // prefix in place, permanently poisoning subsequent route registrations
475 // on the same builder.
476 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| build(self)));
477 self.pending_prefix = saved;
478 if let Err(payload) = result {
479 std::panic::resume_unwind(payload);
480 }
481 self
482 }
483
484 /// Mounts every route from a child router under the given path prefix.
485 ///
486 /// Unlike [`Router::merge`], `nest` builds **new** `Arc<Route>` instances for
487 /// each child route via `Route::cloned_with_path` — so re-nesting the same
488 /// child cannot double-stack its global middleware onto the same shared
489 /// `Arc<Route>`. The child router's global middleware chain is prepended to
490 /// each newly-registered route's middleware chain (so child globals run
491 /// before child-route middleware at dispatch time).
492 ///
493 /// Caveats:
494 /// - Route-level plugins on the child are **not** carried over.
495 /// - The child's fallback / error handlers are **not** inherited.
496 ///
497 /// # Panics
498 ///
499 /// Panics at registration time if mounting the child would conflict with a
500 /// route already present on `self` (same method + same prefixed path).
501 /// Mirrors the behavior of [`Router::route`] — route registration is a
502 /// startup-time operation and conflicts are configuration bugs, not
503 /// runtime conditions.
504 ///
505 /// # Examples
506 ///
507 /// ```rust
508 /// use tako::router::Router;
509 /// use tako::responder::Responder;
510 ///
511 /// async fn list_users() -> impl Responder { "users" }
512 ///
513 /// let mut api = Router::new();
514 /// api.get("/users", list_users);
515 ///
516 /// let mut root = Router::new();
517 /// root.nest("/api/v1", api); // /users → /api/v1/users
518 /// ```
519 pub fn nest(&mut self, prefix: &str, child: Router) -> &mut Self {
520 let upstream_globals = child.middlewares.load_full();
521
522 for (method, weak_vec) in child.routes.iter() {
523 for weak in weak_vec {
524 let Some(child_route) = weak.upgrade() else {
525 continue;
526 };
527
528 let combined = combine_prefix_path(prefix, &child_route.path);
529 let new_path = self.apply_pending_prefix(&combined);
530
531 let new_route = child_route.cloned_with_path(new_path.clone());
532
533 if !upstream_globals.is_empty() {
534 let existing = new_route.middlewares.load_full();
535 let mut merged = Vec::with_capacity(upstream_globals.len() + existing.len());
536 merged.extend(upstream_globals.iter().cloned());
537 merged.extend(existing.iter().cloned());
538 new_route.has_middleware.store(true, Ordering::Release);
539 new_route.middlewares.store(Arc::new(merged));
540 }
541
542 if let Err(err) = self
543 .inner
544 .get_or_default_mut(&method)
545 .insert(new_path, new_route.clone())
546 {
547 panic!("Failed to nest route: {err}");
548 }
549 self
550 .routes
551 .get_or_default_mut(&method)
552 .push(Arc::downgrade(&new_route));
553 }
554 }
555
556 #[cfg(feature = "signals")]
557 self.signals.merge_from(&child.signals);
558
559 self
560 }
561
562 /// Registers a route with trailing slash redirection enabled.
563 ///
564 /// When TSR is enabled, requests to paths with or without trailing slashes
565 /// are automatically redirected to the canonical version. This helps maintain
566 /// consistent URLs and prevents duplicate content issues.
567 ///
568 /// # Panics
569 ///
570 /// - Panics if called with the root path (`"/"`) since TSR is not applicable.
571 /// - Panics if a route with the same method and path pattern is already registered.
572 ///
573 /// # Examples
574 ///
575 /// ```rust
576 /// use tako::{router::Router, Method, responder::Responder, types::Request};
577 ///
578 /// async fn api_handler(_req: Request) -> impl Responder {
579 /// "API endpoint"
580 /// }
581 ///
582 /// let mut router = Router::new();
583 /// // Both "/api" and "/api/" will redirect to the canonical form
584 /// router.route_with_tsr(Method::GET, "/api", api_handler);
585 /// ```
586 pub fn route_with_tsr<H, T>(&mut self, method: Method, path: &str, handler: H) -> Arc<Route>
587 where
588 H: Handler<T> + Clone + 'static,
589 {
590 assert!(path != "/", "Cannot route with TSR for root path");
591
592 let final_path = self.apply_pending_prefix(path);
593 let route = Arc::new(Route::new(
594 final_path.clone(),
595 method.clone(),
596 BoxHandler::new::<H, T>(handler),
597 Some(true),
598 ));
599
600 if let Err(err) = self
601 .inner
602 .get_or_default_mut(&method)
603 .insert(final_path, route.clone())
604 {
605 panic!("Failed to register route: {err}");
606 }
607
608 self
609 .routes
610 .get_or_default_mut(&method)
611 .push(Arc::downgrade(&route));
612
613 route
614 }
615
616 /// Executes the given endpoint through the global middleware chain.
617 ///
618 /// This helper is used for cases like TSR redirects and default 404 responses,
619 /// ensuring that router-level middleware (e.g., CORS) always runs.
620 async fn run_with_global_middlewares_for_endpoint(
621 &self,
622 req: Request,
623 endpoint: BoxHandler,
624 ) -> Response {
625 if self.has_global_middleware.load(Ordering::Acquire) {
626 Next {
627 global_middlewares: self.middlewares.load_full(),
628 route_middlewares: Arc::default(),
629 index: 0,
630 endpoint,
631 }
632 .run(req)
633 .await
634 } else {
635 endpoint.call(req).await
636 }
637 }
638
639 /// Executes the middleware chain with an optional timeout.
640 ///
641 /// If a timeout is specified and exceeded, the timeout fallback handler
642 /// is invoked or a default 408 Request Timeout response is returned.
643 async fn run_with_timeout(
644 &self,
645 req: Request,
646 next: Next,
647 timeout_duration: Option<Duration>,
648 ) -> Response {
649 match timeout_duration {
650 Some(duration) => {
651 #[cfg(not(feature = "compio"))]
652 {
653 match tokio::time::timeout(duration, next.run(req)).await {
654 Ok(response) => response,
655 Err(_elapsed) => self.handle_timeout().await,
656 }
657 }
658 #[cfg(feature = "compio")]
659 {
660 let sleep = std::pin::pin!(compio::time::sleep(duration));
661 let work = std::pin::pin!(next.run(req));
662 match futures_util::future::select(work, sleep).await {
663 futures_util::future::Either::Left((response, _)) => response,
664 futures_util::future::Either::Right(((), _)) => self.handle_timeout().await,
665 }
666 }
667 }
668 None => next.run(req).await,
669 }
670 }
671
672 /// Returns the timeout response using the fallback handler or a default 408.
673 async fn handle_timeout(&self) -> Response {
674 if let Some(handler) = &self.timeout_fallback {
675 handler.call(Request::default()).await
676 } else {
677 empty_status_response(StatusCode::REQUEST_TIMEOUT)
678 }
679 }
680
681 /// Dispatches an incoming request to the appropriate route handler.
682 #[inline]
683 pub async fn dispatch(&self, mut req: Request) -> Response {
684 // Per-router state: only inject when at least one `with_state` was called.
685 // The atomic load is monomorphic and cheap; the Arc clone (atomic incref)
686 // only happens for routers that actually use instance-local state.
687 if self.has_router_state.load(Ordering::Acquire) {
688 req.extensions_mut().insert(Arc::clone(&self.router_state));
689 }
690
691 // App-level request signal — emitted here so every transport gets it for
692 // free without duplicating the boilerplate. The cost is a single string
693 // formatting pair per request and is gated to the `signals` feature.
694 #[cfg(feature = "signals")]
695 let (req_method_str, req_path_str) = (req.method().to_string(), req.uri().path().to_string());
696 #[cfg(feature = "signals")]
697 {
698 SignalArbiter::emit_app(
699 Signal::with_capacity(ids::REQUEST_STARTED, 2)
700 .meta("method", req_method_str.clone())
701 .meta("path", req_path_str.clone()),
702 )
703 .await;
704 }
705
706 // Phase 1: Route lookup using a borrowed path — no String allocation on the
707 // hot path. The block scope ensures all borrows on `req` are released before
708 // we need to mutate it.
709 let route_match = {
710 if let Some(method_router) = self.inner.get(req.method())
711 && let Ok(matched) = method_router.at(req.uri().path())
712 {
713 let route = Arc::clone(matched.value);
714 let mut it = matched.params.iter();
715 let first = it.next();
716 let params = first.map(|(fk, fv)| {
717 let mut p = SmallVec::<[(String, String); 4]>::new();
718 p.push((fk.to_string(), fv.to_string()));
719 for (k, v) in it {
720 p.push((k.to_string(), v.to_string()));
721 }
722 PathParams(p)
723 });
724 Some((route, params))
725 } else {
726 None
727 }
728 };
729
730 // Phase 2: Dispatch — `req` is no longer borrowed, safe to mutate.
731 let response = if let Some((route, params)) = route_match {
732 // Protocol guard: short-circuit dispatch *but fall through* to the shared
733 // completion tail (error-handler + REQUEST_COMPLETED signal). Returning
734 // here would leak the in-flight signal pair (REQUEST_STARTED already
735 // emitted above without a matching REQUEST_COMPLETED).
736 if let Some(res) = Self::enforce_protocol_guard(&route, &req) {
737 res
738 } else {
739 #[cfg(feature = "signals")]
740 let route_signals = route.signal_arbiter();
741
742 // Initialize route-level plugins on first request
743 #[cfg(feature = "plugins")]
744 route.setup_plugins_once();
745
746 // Inject route-level SIMD JSON config into request extensions
747 if let Some(mode) = route.get_simd_json_mode() {
748 req.extensions_mut().insert(mode);
749 }
750
751 if let Some(params) = params {
752 req.extensions_mut().insert(params);
753 }
754
755 // Inject the matched route template (e.g. `/users/{id}`) so handlers
756 // and middleware can label metrics/logs by the routing key, not the
757 // concrete URI.
758 req
759 .extensions_mut()
760 .insert(crate::router_state::MatchedPath(route.path.clone()));
761
762 // Determine effective timeout: route-level overrides router-level
763 let effective_timeout = route.get_timeout().or(self.timeout);
764
765 // Fast atomic check: skip ArcSwap loads entirely when no middleware is registered.
766 let needs_chain = self.has_global_middleware.load(Ordering::Acquire)
767 || route.has_middleware.load(Ordering::Acquire);
768
769 #[cfg(feature = "signals")]
770 {
771 // Reuse the strings already formatted for REQUEST_STARTED instead of
772 // re-allocating per request on the hot path. Cheap `String::clone` is
773 // a single Vec dup; route-level signals consume the clones for the
774 // STARTED emission and the final move into ROUTE_REQUEST_COMPLETED.
775 let method_str = req_method_str.clone();
776 let path_str = req_path_str.clone();
777 let route_template = route.path.clone();
778
779 route_signals
780 .emit(
781 Signal::with_capacity(ids::ROUTE_REQUEST_STARTED, 3)
782 .meta("method", method_str.clone())
783 .meta("path", path_str.clone())
784 .meta("route", route_template.clone()),
785 )
786 .await;
787
788 let response = if !needs_chain && effective_timeout.is_none() {
789 route.handler.call(req).await
790 } else {
791 let next = Next {
792 global_middlewares: self.middlewares.load_full(),
793 route_middlewares: route.middlewares.load_full(),
794 index: 0,
795 endpoint: route.handler.clone(),
796 };
797 self.run_with_timeout(req, next, effective_timeout).await
798 };
799
800 route_signals
801 .emit(
802 Signal::with_capacity(ids::ROUTE_REQUEST_COMPLETED, 4)
803 .meta("method", method_str)
804 .meta("path", path_str)
805 .meta("route", route_template)
806 .meta("status", response.status().as_u16().to_string()),
807 )
808 .await;
809
810 response
811 }
812
813 #[cfg(not(feature = "signals"))]
814 {
815 if !needs_chain && effective_timeout.is_none() {
816 route.handler.call(req).await
817 } else {
818 let next = Next {
819 global_middlewares: self.middlewares.load_full(),
820 route_middlewares: route.middlewares.load_full(),
821 index: 0,
822 endpoint: route.handler.clone(),
823 };
824 self.run_with_timeout(req, next, effective_timeout).await
825 }
826 }
827 }
828 } else {
829 // Cold path: no direct match — try TSR redirect / 405 / fallback.
830 // String allocation is acceptable here.
831 let tsr_path = {
832 let p = req.uri().path();
833 if p.ends_with('/') {
834 p.trim_end_matches('/').to_string()
835 } else {
836 format!("{p}/")
837 }
838 };
839
840 if let Some(method_router) = self.inner.get(req.method())
841 && let Ok(matched) = method_router.at(&tsr_path)
842 && matched.value.tsr
843 {
844 let handler = move |_req: Request| {
845 let tsr_path = tsr_path.clone();
846 async move {
847 // `tsr_path` is reconstructed from registered route segments and
848 // the incoming URI path. It can technically contain bytes that
849 // are invalid in an HTTP header value (CR/LF/NUL) if the request
850 // path is crafted maliciously — in that case fall back to a
851 // bare 308 without a `Location` header rather than panicking.
852 match http::HeaderValue::from_str(&tsr_path) {
853 Ok(loc) => {
854 let mut resp = empty_status_response(StatusCode::TEMPORARY_REDIRECT);
855 resp.headers_mut().insert(http::header::LOCATION, loc);
856 resp
857 }
858 Err(_) => empty_status_response(StatusCode::TEMPORARY_REDIRECT),
859 }
860 }
861 };
862
863 self
864 .run_with_global_middlewares_for_endpoint(req, BoxHandler::new::<_, (Request,)>(handler))
865 .await
866 } else {
867 // Method-mismatch detection: if the same path is registered for any
868 // *other* method, RFC 9110 mandates 405 with an `Allow` header rather
869 // than 404. This is the cold path; iterating the 9 standard methods
870 // is cheap.
871 let allowed = self.collect_allowed_methods(req.uri().path());
872 if !allowed.is_empty() {
873 let allow_value = join_methods(&allowed);
874 let handler = move |_req: Request| {
875 let allow_value = allow_value.clone();
876 async move {
877 // `allow_value` is built from `Method::as_str()` for the
878 // registered methods, so it only contains ASCII method tokens
879 // — `HeaderValue::from_str` is statically infallible. Use the
880 // fallible API and ignore the impossible error rather than
881 // panicking.
882 let mut resp = empty_status_response(StatusCode::METHOD_NOT_ALLOWED);
883 if let Ok(v) = http::HeaderValue::from_str(&allow_value) {
884 resp.headers_mut().insert(http::header::ALLOW, v);
885 }
886 resp
887 }
888 };
889 self
890 .run_with_global_middlewares_for_endpoint(
891 req,
892 BoxHandler::new::<_, (Request,)>(handler),
893 )
894 .await
895 } else if let Some(handler) = &self.fallback {
896 self
897 .run_with_global_middlewares_for_endpoint(req, handler.clone())
898 .await
899 } else {
900 let handler = |_req: Request| async { empty_status_response(StatusCode::NOT_FOUND) };
901
902 self
903 .run_with_global_middlewares_for_endpoint(
904 req,
905 BoxHandler::new::<_, (Request,)>(handler),
906 )
907 .await
908 }
909 }
910 };
911
912 let response = self.maybe_apply_error_handler(response);
913
914 #[cfg(feature = "signals")]
915 {
916 SignalArbiter::emit_app(
917 Signal::with_capacity(ids::REQUEST_COMPLETED, 3)
918 .meta("method", req_method_str)
919 .meta("path", req_path_str)
920 .meta("status", response.status().as_u16().to_string()),
921 )
922 .await;
923 }
924
925 response
926 }
927
928 /// Applies the appropriate error handler if one is set:
929 /// - 5xx → [`Router::error_handler`]
930 /// - 4xx → [`Router::client_error_handler`]
931 fn maybe_apply_error_handler(&self, response: Response) -> Response {
932 let status = response.status();
933 if status.is_server_error() {
934 if let Some(handler) = &self.error_handler {
935 return handler(response);
936 }
937 } else if status.is_client_error()
938 && let Some(handler) = &self.client_error_handler
939 {
940 return handler(response);
941 }
942 response
943 }
944
945 /// Adds a value to the global type-based state accessible by all handlers.
946 ///
947 /// Global state allows sharing data across different routes and middleware.
948 /// Values are stored by their concrete type and retrieved via the
949 /// `State` extractor (from `tako-extractors`) or with
950 /// [`crate::state::get_state`].
951 ///
952 /// # Examples
953 ///
954 /// ```rust
955 /// use tako::router::Router;
956 ///
957 /// #[derive(Clone)]
958 /// struct AppConfig { database_url: String, api_key: String }
959 ///
960 /// let mut router = Router::new();
961 /// router.state(AppConfig {
962 /// database_url: "postgresql://localhost/mydb".to_string(),
963 /// api_key: "secret-key".to_string(),
964 /// });
965 /// // You can also store simple types by type:
966 /// router.state::<String>("1.0.0".to_string());
967 /// ```
968 pub fn state<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
969 set_state(value);
970 }
971
972 /// Inserts a value into this router's instance-local typed state.
973 ///
974 /// Unlike [`Router::state`] (which writes the process-global registry and
975 /// therefore allows only one value per `T` per process), `with_state` is
976 /// per-router — multiple routers can hold distinct `T`s without collisions.
977 ///
978 /// The `State` extractor (from `tako-extractors`) reads the per-router
979 /// store first and falls back to the global store if no per-router value
980 /// exists, so existing code that uses `set_state` / `Router::state`
981 /// continues to work unchanged.
982 ///
983 /// Hot-path cost is one `Arc` clone per request *only when* at least one
984 /// `with_state` call has happened on this router; an `AtomicBool::Acquire`
985 /// fast-path skips it for routers that don't use instance-local state.
986 ///
987 /// # Examples
988 ///
989 /// ```rust
990 /// use tako::router::Router;
991 ///
992 /// #[derive(Clone)]
993 /// struct Db;
994 ///
995 /// let mut router = Router::new();
996 /// router.with_state(Db);
997 /// ```
998 pub fn with_state<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> &mut Self {
999 self.router_state.insert(value);
1000 self.has_router_state.store(true, Ordering::Release);
1001 self
1002 }
1003
1004 /// Returns the per-router typed state (shared `Arc`).
1005 #[inline]
1006 pub fn router_state(&self) -> &Arc<RouterState> {
1007 &self.router_state
1008 }
1009
1010 #[cfg(feature = "signals")]
1011 /// Returns a reference to the signal arbiter.
1012 pub fn signals(&self) -> &SignalArbiter {
1013 &self.signals
1014 }
1015
1016 #[cfg(feature = "signals")]
1017 /// Returns a clone of the signal arbiter, useful for sharing through state.
1018 pub fn signal_arbiter(&self) -> SignalArbiter {
1019 self.signals.clone()
1020 }
1021
1022 #[cfg(feature = "signals")]
1023 /// Registers a handler for a named signal on this router's arbiter.
1024 pub fn on_signal<F, Fut>(&self, id: impl Into<String>, handler: F)
1025 where
1026 F: Fn(Signal) -> Fut + Send + Sync + 'static,
1027 Fut: std::future::Future<Output = ()> + Send + 'static,
1028 {
1029 self.signals.on(id, handler);
1030 }
1031
1032 #[cfg(feature = "signals")]
1033 /// Emits a signal through this router's arbiter.
1034 pub async fn emit_signal(&self, signal: Signal) {
1035 self.signals.emit(signal).await;
1036 }
1037
1038 /// Adds global middleware to the router.
1039 ///
1040 /// Global middleware is executed for all routes in the order it was added,
1041 /// before any route-specific middleware. Middleware can modify requests,
1042 /// generate responses, or perform side effects like logging or authentication.
1043 ///
1044 /// # Examples
1045 ///
1046 /// ```rust
1047 /// use tako::{router::Router, middleware::Next, types::Request};
1048 ///
1049 /// let mut router = Router::new();
1050 ///
1051 /// // Logging middleware
1052 /// router.middleware(|req, next| async move {
1053 /// println!("Request: {} {}", req.method(), req.uri());
1054 /// let response = next.run(req).await;
1055 /// println!("Response: {}", response.status());
1056 /// response
1057 /// });
1058 ///
1059 /// // Authentication middleware
1060 /// router.middleware(|req, next| async move {
1061 /// if req.headers().contains_key("authorization") {
1062 /// next.run(req).await
1063 /// } else {
1064 /// "Unauthorized".into_response()
1065 /// }
1066 /// });
1067 /// ```
1068 pub fn middleware<F, Fut, R>(&self, f: F) -> &Self
1069 where
1070 F: Fn(Request, Next) -> Fut + Clone + Send + Sync + 'static,
1071 Fut: std::future::Future<Output = R> + Send + 'static,
1072 R: Responder + Send + 'static,
1073 {
1074 let mw: BoxMiddleware = Arc::new(move |req, next| {
1075 let fut = f(req, next);
1076 Box::pin(async move { fut.await.into_response() })
1077 });
1078
1079 // RCU-style append: rebuild the Vec atomically against concurrent pushers.
1080 // ArcSwap retries the closure on CAS conflict, so concurrent middleware
1081 // registrations cannot lose entries.
1082 self.middlewares.rcu(move |current| {
1083 let mut next = Vec::with_capacity(current.len() + 1);
1084 next.extend(current.iter().cloned());
1085 next.push(mw.clone());
1086 Arc::new(next)
1087 });
1088 self.has_global_middleware.store(true, Ordering::Release);
1089 self
1090 }
1091
1092 /// Sets a fallback handler that will be executed when no route matches.
1093 ///
1094 /// The fallback runs after global middlewares and can be used to implement
1095 /// custom 404 pages, catch-all logic, or method-independent handlers.
1096 ///
1097 /// # Examples
1098 ///
1099 /// ```rust
1100 /// use tako::{router::Router, Method, responder::Responder, types::Request};
1101 ///
1102 /// async fn not_found(_req: Request) -> impl Responder { "Not Found" }
1103 ///
1104 /// let mut router = Router::new();
1105 /// router.route(Method::GET, "/", |_req| async { "Hello" });
1106 /// router.fallback(not_found);
1107 /// ```
1108 pub fn fallback<F, Fut, R>(&mut self, handler: F) -> &mut Self
1109 where
1110 F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
1111 Fut: std::future::Future<Output = R> + Send + 'static,
1112 R: Responder + Send + 'static,
1113 {
1114 // Use the Request-arg handler impl to box the fallback
1115 self.fallback = Some(BoxHandler::new::<F, (Request,)>(handler));
1116 self
1117 }
1118
1119 /// Sets a fallback handler that supports extractors (like `Path`, `Query`, etc.).
1120 ///
1121 /// Use this when your fallback needs to parse request data via extractors. If you
1122 /// only need access to the raw `Request`, prefer `fallback` for simpler type inference.
1123 ///
1124 /// # Examples
1125 ///
1126 /// ```rust
1127 /// use tako::{router::Router, responder::Responder, extractors::{path::Path, query::Query}};
1128 ///
1129 /// #[derive(serde::Deserialize)]
1130 /// struct Q { q: Option<String> }
1131 ///
1132 /// async fn fallback_with_q(Path(_p): Path<String>, Query(_q): Query<Q>) -> impl Responder {
1133 /// "Not Found"
1134 /// }
1135 ///
1136 /// let mut router = Router::new();
1137 /// router.fallback_with_extractors(fallback_with_q);
1138 /// ```
1139 pub fn fallback_with_extractors<H, T>(&mut self, handler: H) -> &mut Self
1140 where
1141 H: Handler<T> + Clone + 'static,
1142 {
1143 self.fallback = Some(BoxHandler::new::<H, T>(handler));
1144 self
1145 }
1146
1147 /// Sets a default timeout for all routes.
1148 ///
1149 /// This timeout can be overridden on individual routes using `Route::timeout`.
1150 /// When a request exceeds the timeout duration, the timeout fallback handler
1151 /// is invoked (if configured) or a 408 Request Timeout response is returned.
1152 ///
1153 /// # Examples
1154 ///
1155 /// ```rust
1156 /// use tako::router::Router;
1157 /// use std::time::Duration;
1158 ///
1159 /// let mut router = Router::new();
1160 /// router.timeout(Duration::from_secs(30));
1161 /// ```
1162 pub fn timeout(&mut self, duration: Duration) -> &mut Self {
1163 self.timeout = Some(duration);
1164 self
1165 }
1166
1167 /// Sets a fallback handler that will be executed when a request times out.
1168 ///
1169 /// If no timeout fallback is set, a default 408 Request Timeout response is returned.
1170 ///
1171 /// # Examples
1172 ///
1173 /// ```rust
1174 /// use tako::{router::Router, responder::Responder, types::Request};
1175 /// use std::time::Duration;
1176 ///
1177 /// async fn timeout_handler(_req: Request) -> impl Responder {
1178 /// "Request took too long"
1179 /// }
1180 ///
1181 /// let mut router = Router::new();
1182 /// router.timeout(Duration::from_secs(30));
1183 /// router.timeout_fallback(timeout_handler);
1184 /// ```
1185 pub fn timeout_fallback<F, Fut, R>(&mut self, handler: F) -> &mut Self
1186 where
1187 F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
1188 Fut: std::future::Future<Output = R> + Send + 'static,
1189 R: Responder + Send + 'static,
1190 {
1191 self.timeout_fallback = Some(BoxHandler::new::<F, (Request,)>(handler));
1192 self
1193 }
1194
1195 /// Sets a global error handler for 5xx responses.
1196 ///
1197 /// The error handler receives any response with a server error status and can
1198 /// transform it (e.g., to return JSON-formatted errors instead of plain text).
1199 ///
1200 /// # Examples
1201 ///
1202 /// ```rust
1203 /// use tako::router::Router;
1204 /// use tako::body::TakoBody;
1205 ///
1206 /// let mut router = Router::new();
1207 /// router.error_handler(|resp| {
1208 /// let status = resp.status();
1209 /// let body = format!(r#"{{"error": "{}"}}"#, status.canonical_reason().unwrap_or("Unknown"));
1210 /// let mut res = http::Response::new(TakoBody::from(body));
1211 /// *res.status_mut() = status;
1212 /// res.headers_mut().insert(
1213 /// http::header::CONTENT_TYPE,
1214 /// http::HeaderValue::from_static("application/json"),
1215 /// );
1216 /// res
1217 /// });
1218 /// ```
1219 pub fn error_handler(
1220 &mut self,
1221 handler: impl Fn(Response) -> Response + Send + Sync + 'static,
1222 ) -> &mut Self {
1223 self.error_handler = Some(Arc::new(handler));
1224 self
1225 }
1226
1227 /// Sets a global error handler for 4xx responses.
1228 ///
1229 /// Mirrors [`Router::error_handler`] but fires for client errors. Useful for
1230 /// converting bare 404 / 405 / 422 responses into structured error documents
1231 /// (e.g. via [`crate::problem::default_problem_responder`]).
1232 pub fn client_error_handler(
1233 &mut self,
1234 handler: impl Fn(Response) -> Response + Send + Sync + 'static,
1235 ) -> &mut Self {
1236 self.client_error_handler = Some(Arc::new(handler));
1237 self
1238 }
1239
1240 /// Convenience: install [`crate::problem::default_problem_responder`] for
1241 /// both 4xx and 5xx so unhandled errors always render as
1242 /// `application/problem+json`.
1243 pub fn use_problem_json(&mut self) -> &mut Self {
1244 let h: ErrorHandler = Arc::new(crate::problem::default_problem_responder);
1245 self.error_handler = Some(h.clone());
1246 self.client_error_handler = Some(h);
1247 self
1248 }
1249
1250 /// Registers a plugin with the router.
1251 ///
1252 /// Plugins extend the router's functionality by providing additional features
1253 /// like compression, CORS handling, rate limiting, or custom behavior. Plugins
1254 /// are initialized once when the server starts.
1255 ///
1256 /// # Examples
1257 ///
1258 /// ```rust
1259 /// # #[cfg(feature = "plugins")]
1260 /// use tako::{router::Router, plugins::TakoPlugin};
1261 /// # #[cfg(feature = "plugins")]
1262 /// use anyhow::Result;
1263 ///
1264 /// # #[cfg(feature = "plugins")]
1265 /// struct LoggingPlugin;
1266 ///
1267 /// # #[cfg(feature = "plugins")]
1268 /// impl TakoPlugin for LoggingPlugin {
1269 /// fn name(&self) -> &'static str {
1270 /// "logging"
1271 /// }
1272 ///
1273 /// fn setup(&self, _router: &Router) -> Result<()> {
1274 /// println!("Logging plugin initialized");
1275 /// Ok(())
1276 /// }
1277 /// }
1278 ///
1279 /// # #[cfg(feature = "plugins")]
1280 /// # fn example() {
1281 /// let mut router = Router::new();
1282 /// router.plugin(LoggingPlugin);
1283 /// # }
1284 /// ```
1285 #[cfg(feature = "plugins")]
1286 #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
1287 pub fn plugin<P>(&mut self, plugin: P) -> &mut Self
1288 where
1289 P: TakoPlugin + Clone + Send + Sync + 'static,
1290 {
1291 self.plugins.push(Box::new(plugin));
1292 self
1293 }
1294
1295 /// Returns references to all registered plugins.
1296 #[cfg(feature = "plugins")]
1297 #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
1298 pub(crate) fn plugins(&self) -> Vec<&dyn TakoPlugin> {
1299 self.plugins.iter().map(AsRef::as_ref).collect()
1300 }
1301
1302 /// Initializes all registered plugins exactly once.
1303 #[cfg(feature = "plugins")]
1304 #[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
1305 #[doc(hidden)]
1306 pub fn setup_plugins_once(&self) {
1307 use std::sync::atomic::Ordering;
1308
1309 // Hot-path fast exit: see `Route::setup_plugins_once`. Acquire-load
1310 // pairs with the Release half of the swap so plugin-published state
1311 // is visible by the time we skip the RMW.
1312 if self.plugins_initialized.load(Ordering::Acquire) {
1313 return;
1314 }
1315
1316 if !self.plugins_initialized.swap(true, Ordering::SeqCst) {
1317 for plugin in self.plugins() {
1318 // Surface plugin setup errors loudly — a silently-skipped CORS,
1319 // auth, rate-limit, or CSRF plugin would leave the server
1320 // running without the protection the operator expected
1321 // (security-relevant fail-open). Cold path — first dispatch only.
1322 if let Err(e) = plugin.setup(self) {
1323 tracing::error!(
1324 plugin = plugin.name(),
1325 error = %e,
1326 "router-level TakoPlugin::setup failed; plugin not active"
1327 );
1328 }
1329 }
1330 }
1331 }
1332
1333 /// Merges another router into this router.
1334 ///
1335 /// This method combines routes and middleware from another router into the
1336 /// current one. Routes are copied over, and the other router's global middleware
1337 /// is prepended to each merged route's middleware chain.
1338 ///
1339 /// # Panics
1340 ///
1341 /// Panics at registration time if a merged route conflicts with one already
1342 /// present on `self` (same method + same path). Mirrors the behavior of
1343 /// [`Router::route`] and [`Router::nest`] — merge is a startup-time
1344 /// operation and route conflicts are configuration bugs.
1345 ///
1346 /// # Examples
1347 ///
1348 /// ```rust
1349 /// use tako::{router::Router, Method, responder::Responder, types::Request};
1350 ///
1351 /// async fn api_handler(_req: Request) -> impl Responder {
1352 /// "API response"
1353 /// }
1354 ///
1355 /// async fn web_handler(_req: Request) -> impl Responder {
1356 /// "Web response"
1357 /// }
1358 ///
1359 /// // Create API router
1360 /// let mut api_router = Router::new();
1361 /// api_router.route(Method::GET, "/users", api_handler);
1362 /// api_router.middleware(|req, next| async move {
1363 /// println!("API middleware");
1364 /// next.run(req).await
1365 /// });
1366 ///
1367 /// // Create main router and merge API router
1368 /// let mut main_router = Router::new();
1369 /// main_router.route(Method::GET, "/", web_handler);
1370 /// main_router.merge(api_router);
1371 /// ```
1372 pub fn merge(&mut self, other: Router) {
1373 let upstream_globals = other.middlewares.load_full();
1374
1375 for (method, weak_vec) in other.routes.iter() {
1376 for weak in weak_vec {
1377 if let Some(child_route) = weak.upgrade() {
1378 // Re-issue the route as a fresh `Arc<Route>` (same path) so we do
1379 // not mutate the child's middleware chain in-place — other router
1380 // instances may still hold the original `Arc` and would observe
1381 // unrelated middleware insertions otherwise.
1382 let new_route = child_route.cloned_with_path(child_route.path.clone());
1383
1384 if !upstream_globals.is_empty() {
1385 let existing = new_route.middlewares.load_full();
1386 let mut merged = Vec::with_capacity(upstream_globals.len() + existing.len());
1387 merged.extend(upstream_globals.iter().cloned());
1388 merged.extend(existing.iter().cloned());
1389 new_route.has_middleware.store(true, Ordering::Release);
1390 new_route.middlewares.store(Arc::new(merged));
1391 }
1392
1393 // Match `nest` semantics: a path conflict is a builder bug, not a
1394 // silent overwrite. Returning early via `let _ = … insert` would
1395 // throw away the existing route under a stable URL.
1396 if let Err(err) = self
1397 .inner
1398 .get_or_default_mut(&method)
1399 .insert(new_route.path.clone(), new_route.clone())
1400 {
1401 panic!(
1402 "Failed to merge route '{}' (method {:?}): {err}",
1403 new_route.path, method
1404 );
1405 }
1406
1407 self
1408 .routes
1409 .get_or_default_mut(&method)
1410 .push(Arc::downgrade(&new_route));
1411 }
1412 }
1413 }
1414
1415 #[cfg(feature = "signals")]
1416 self.signals.merge_from(&other.signals);
1417 }
1418
1419 /// Returns every method that has a route matching the given path.
1420 ///
1421 /// Used by the 405 / `Allow` cold-path branch in [`Router::dispatch`]; not on
1422 /// the fast path. Iterates all standard methods (O(9)) plus any custom ones.
1423 fn collect_allowed_methods(&self, path: &str) -> SmallVec<[Method; 4]> {
1424 let mut allowed = SmallVec::<[Method; 4]>::new();
1425 for (method, m) in self.inner.iter() {
1426 if m.at(path).is_ok() {
1427 allowed.push(method);
1428 }
1429 }
1430 allowed
1431 }
1432
1433 /// Ensures the request HTTP version satisfies the route's configured protocol guard.
1434 /// Returns `Some(Response)` with 505 HTTP Version Not Supported when the request
1435 /// doesn't match the guard, otherwise returns `None` to continue dispatch.
1436 fn enforce_protocol_guard(route: &Route, req: &Request) -> Option<Response> {
1437 if let Some(guard) = route.protocol_guard()
1438 && guard != req.version()
1439 {
1440 return Some(empty_status_response(
1441 StatusCode::HTTP_VERSION_NOT_SUPPORTED,
1442 ));
1443 }
1444 None
1445 }
1446
1447 // OpenAPI route collection
1448
1449 /// Collects `OpenAPI` metadata from all registered routes.
1450 ///
1451 /// Returns a vector of tuples containing the HTTP method, path, and `OpenAPI`
1452 /// metadata for each route that has `OpenAPI` information attached.
1453 ///
1454 /// # Examples
1455 ///
1456 /// ```rust,ignore
1457 /// use tako::{router::Router, Method};
1458 ///
1459 /// let mut router = Router::new();
1460 /// router.route(Method::GET, "/users", list_users)
1461 /// .summary("List users")
1462 /// .tag("users");
1463 ///
1464 /// for (method, path, openapi) in router.collect_openapi_routes() {
1465 /// println!("{} {} - {:?}", method, path, openapi.summary);
1466 /// }
1467 /// ```
1468 #[cfg(any(feature = "utoipa", feature = "vespera"))]
1469 #[cfg_attr(docsrs, doc(cfg(any(feature = "utoipa", feature = "vespera"))))]
1470 pub fn collect_openapi_routes(&self) -> Vec<(Method, String, crate::openapi::RouteOpenApi)> {
1471 let mut result = Vec::new();
1472
1473 for (method, weak_vec) in self.routes.iter() {
1474 for weak in weak_vec {
1475 if let Some(route) = weak.upgrade()
1476 && let Some(openapi) = route.openapi_metadata()
1477 {
1478 result.push((method.clone(), route.path.clone(), openapi));
1479 }
1480 }
1481 }
1482
1483 result
1484 }
1485
1486 /// Drops dangling `Weak<Route>` entries from the per-method `routes` index.
1487 ///
1488 /// All current routes stay live for the router's lifetime, so this is a
1489 /// no-op in well-behaved code. It exists as a safety valve: if any future
1490 /// API ever removes from `inner` (hot reload, route deregistration), or if
1491 /// downstream code holds the `Arc<Route>` returned from [`Router::route`]
1492 /// past the router's lifetime, this method bounds the size of the index.
1493 ///
1494 /// Cold path; safe to call repeatedly. Linear in the total number of
1495 /// registered routes.
1496 pub fn compact_routes(&mut self) {
1497 for weak_vec in self.routes.iter_mut() {
1498 weak_vec.retain(|w| w.strong_count() > 0);
1499 }
1500 }
1501}
1502
1503/// Joins a path prefix and a child path, normalising the boundary slash.
1504fn combine_prefix_path(prefix: &str, path: &str) -> String {
1505 if prefix.is_empty() || prefix == "/" {
1506 return path.to_string();
1507 }
1508 let prefix = prefix.trim_end_matches('/');
1509 if path.is_empty() || path == "/" {
1510 return prefix.to_string();
1511 }
1512 if path.starts_with('/') {
1513 let mut out = String::with_capacity(prefix.len() + path.len());
1514 out.push_str(prefix);
1515 out.push_str(path);
1516 out
1517 } else {
1518 let mut out = String::with_capacity(prefix.len() + 1 + path.len());
1519 out.push_str(prefix);
1520 out.push('/');
1521 out.push_str(path);
1522 out
1523 }
1524}
1525
1526/// Joins a slice of HTTP methods into a comma-separated `Allow`-header value.
1527fn join_methods(methods: &[Method]) -> String {
1528 let mut out = String::with_capacity(methods.len() * 8);
1529 for (i, m) in methods.iter().enumerate() {
1530 if i > 0 {
1531 out.push_str(", ");
1532 }
1533 out.push_str(m.as_str());
1534 }
1535 out
1536}
1537
1538/// Distributed slice of route registration thunks.
1539///
1540/// Each `#[tako::route]` / `#[tako::get]` / etc. attribute contributes a
1541/// `fn(&mut Router)` closure that calls [`Router::route`] with the
1542/// generated `Params::METHOD` / `Params::PATH` and the handler. Iterating
1543/// the slice — what [`Router::mount_all`] does — replays every contribution
1544/// against the supplied router.
1545#[linkme::distributed_slice]
1546pub static TAKO_ROUTES: [fn(&mut Router)] = [..];
1547
1548/// Maps the 9 standard HTTP methods to array indices.
1549/// Returns `None` for non-standard / extension methods.
1550#[inline]
1551fn method_slot(method: &Method) -> Option<usize> {
1552 Some(match *method {
1553 Method::GET => 0,
1554 Method::POST => 1,
1555 Method::PUT => 2,
1556 Method::DELETE => 3,
1557 Method::PATCH => 4,
1558 Method::HEAD => 5,
1559 Method::OPTIONS => 6,
1560 Method::CONNECT => 7,
1561 Method::TRACE => 8,
1562 _ => return None,
1563 })
1564}
1565
1566/// Reconstructs a `Method` from its slot index.
1567///
1568/// The only caller iterates `0..9` over the `standard` array, so out-of-range
1569/// indices indicate an internal invariant violation. In debug builds this
1570/// trips an assertion; in release we degrade to `Method::GET` rather than
1571/// panic from a hot router path.
1572#[inline]
1573fn method_from_slot(idx: usize) -> Method {
1574 match idx {
1575 0 => Method::GET,
1576 1 => Method::POST,
1577 2 => Method::PUT,
1578 3 => Method::DELETE,
1579 4 => Method::PATCH,
1580 5 => Method::HEAD,
1581 6 => Method::OPTIONS,
1582 7 => Method::CONNECT,
1583 8 => Method::TRACE,
1584 _ => {
1585 debug_assert!(false, "method_from_slot called with idx={idx}");
1586 Method::GET
1587 }
1588 }
1589}
1590
1591/// A compact, cache-friendly map keyed by HTTP method.
1592///
1593/// Standard methods (GET, POST, PUT, …) use O(1) array indexing.
1594/// Non-standard methods fall back to linear scan (extremely rare in practice).
1595struct MethodMap<V> {
1596 standard: [Option<V>; 9],
1597 custom: Vec<(Method, V)>,
1598}
1599
1600impl<V> MethodMap<V> {
1601 fn new() -> Self {
1602 Self {
1603 standard: std::array::from_fn(|_| None),
1604 custom: Vec::new(),
1605 }
1606 }
1607
1608 /// O(1) lookup for standard methods, linear scan for custom.
1609 #[inline]
1610 fn get(&self, method: &Method) -> Option<&V> {
1611 if let Some(idx) = method_slot(method) {
1612 self.standard[idx].as_ref()
1613 } else {
1614 self
1615 .custom
1616 .iter()
1617 .find(|(m, _)| m == method)
1618 .map(|(_, v)| v)
1619 }
1620 }
1621
1622 /// Returns a mutable reference, inserting `V::default()` if absent.
1623 fn get_or_default_mut(&mut self, method: &Method) -> &mut V
1624 where
1625 V: Default,
1626 {
1627 if let Some(idx) = method_slot(method) {
1628 self.standard[idx].get_or_insert_with(V::default)
1629 } else {
1630 let pos = self.custom.iter().position(|(m, _)| m == method);
1631 if let Some(pos) = pos {
1632 &mut self.custom[pos].1
1633 } else {
1634 self.custom.push((method.clone(), V::default()));
1635 // SAFETY-style invariant: we just pushed, so the vec is non-empty.
1636 // Using `expect` over `unwrap` to surface the invariant if it ever
1637 // breaks in a future refactor.
1638 &mut self
1639 .custom
1640 .last_mut()
1641 .expect("custom vec must contain the entry we just pushed")
1642 .1
1643 }
1644 }
1645 }
1646
1647 /// Iterates over all `(Method, &V)` pairs (standard then custom).
1648 fn iter(&self) -> impl Iterator<Item = (Method, &V)> {
1649 self
1650 .standard
1651 .iter()
1652 .enumerate()
1653 .filter_map(|(idx, slot)| slot.as_ref().map(|v| (method_from_slot(idx), v)))
1654 .chain(self.custom.iter().map(|(m, v)| (m.clone(), v)))
1655 }
1656
1657 /// Mutable counterpart of [`MethodMap::iter`]. Used by router GC paths.
1658 fn iter_mut(&mut self) -> impl Iterator<Item = &mut V> {
1659 self
1660 .standard
1661 .iter_mut()
1662 .filter_map(|slot| slot.as_mut())
1663 .chain(self.custom.iter_mut().map(|(_, v)| v))
1664 }
1665}