Skip to main content

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}