Skip to main content

tako_rs_core/route/
def.rs

1//! The [`Route`] struct, its handler/middleware storage, and construction.
2//!
3//! Holds the field layout for a route (path, method, handler, middleware
4//! chain, protocol guard, and feature-gated plugin / signal / `OpenAPI`
5//! state) plus the constructor and the `cloned_with_path` helper used by
6//! the router to re-home routes under a prefix.
7
8use std::sync::Arc;
9use std::sync::OnceLock;
10use std::sync::atomic::AtomicBool;
11use std::sync::atomic::Ordering;
12use std::time::Duration;
13
14use arc_swap::ArcSwap;
15use http::Method;
16#[cfg(any(feature = "plugins", feature = "utoipa", feature = "vespera"))]
17use parking_lot::RwLock;
18
19use crate::extractors::json::SimdJsonMode;
20use crate::handler::BoxHandler;
21#[cfg(any(feature = "utoipa", feature = "vespera"))]
22use crate::openapi::RouteOpenApi;
23#[cfg(feature = "plugins")]
24use crate::plugins::TakoPlugin;
25#[cfg(feature = "signals")]
26use crate::signals::SignalArbiter;
27use crate::types::BoxMiddleware;
28
29/// HTTP route with path pattern matching and middleware support.
30#[doc(alias = "route")]
31pub struct Route {
32  /// Original path string used to create this route.
33  pub path: String,
34  /// HTTP method this route responds to.
35  pub method: Method,
36  /// Handler function to execute when route is matched.
37  ///
38  /// Crate-private — `BoxHandler` is itself crate-private and external users
39  /// only see `Route` behind `Arc<Route>` via [`Router::routes`], which makes
40  /// the field unusable from downstream code regardless of visibility. Kept
41  /// crate-visible so the dispatch path in `router.rs` can clone/call it.
42  pub(crate) handler: BoxHandler,
43  /// Route-specific middleware chain.
44  ///
45  /// Crate-private to protect the `has_middleware` shortcut: every mutation
46  /// must go through [`Route::middleware`] so the atomic flag stays in sync
47  /// with the `ArcSwap` contents. Direct `ArcSwap::store` from outside would
48  /// silently desynchronize the hot-path skip in the router.
49  pub(crate) middlewares: ArcSwap<Vec<BoxMiddleware>>,
50  /// Fast check: true when route middleware is registered (avoids `ArcSwap` load on hot path).
51  pub(crate) has_middleware: AtomicBool,
52  /// Whether trailing slash redirection is enabled.
53  pub tsr: bool,
54  /// Route-specific plugins.
55  #[cfg(feature = "plugins")]
56  pub(crate) plugins: RwLock<Vec<Box<dyn TakoPlugin>>>,
57  /// Flag to ensure route plugins are initialized only once.
58  #[cfg(feature = "plugins")]
59  pub(crate) plugins_initialized: AtomicBool,
60  /// HTTP protocol version guard (set once via [`Route::version`] / `h09`/`h10`/`h11`/`h2`).
61  pub(crate) http_protocol: OnceLock<http::Version>,
62  /// Route-level signal arbiter.
63  #[cfg(feature = "signals")]
64  pub(crate) signals: SignalArbiter,
65  /// `OpenAPI` metadata for this route.
66  #[cfg(any(feature = "utoipa", feature = "vespera"))]
67  pub(crate) openapi: RwLock<Option<RouteOpenApi>>,
68  /// Route-specific timeout override (set once at registration, lock-free reads).
69  pub(crate) timeout: OnceLock<Duration>,
70  /// Route-level SIMD JSON dispatch mode (set once at registration, lock-free reads).
71  pub(crate) simd_json_mode: OnceLock<SimdJsonMode>,
72}
73
74impl Route {
75  /// Creates a new route with the specified path, method, and handler.
76  pub fn new(path: String, method: Method, handler: BoxHandler, tsr: Option<bool>) -> Self {
77    Self {
78      path,
79      method,
80      handler,
81      middlewares: ArcSwap::new(Arc::default()),
82      has_middleware: AtomicBool::new(false),
83      tsr: tsr.unwrap_or(false),
84      #[cfg(feature = "plugins")]
85      plugins: RwLock::new(Vec::new()),
86      #[cfg(feature = "plugins")]
87      plugins_initialized: AtomicBool::new(false),
88      http_protocol: OnceLock::new(),
89      #[cfg(feature = "signals")]
90      signals: SignalArbiter::new(),
91      #[cfg(any(feature = "utoipa", feature = "vespera"))]
92      openapi: RwLock::new(None),
93      timeout: OnceLock::new(),
94      simd_json_mode: OnceLock::new(),
95    }
96  }
97
98  /// Builds a new `Arc<Route>` with the same handler / middlewares / config
99  /// but a different path. Used by [`crate::router::Router::nest`] to register
100  /// a child router's routes under a prefix without mutating the originals.
101  ///
102  /// Route-level plugins are *not* carried over — `TakoPlugin` is not `Clone`,
103  /// and the cloned route is treated as already-initialized so the empty
104  /// plugin list is never set up. Plugin-bearing routes should be registered
105  /// directly on the parent router after `nest`.
106  pub(crate) fn cloned_with_path(&self, new_path: String) -> Arc<Route> {
107    let cloned = Self {
108      path: new_path,
109      method: self.method.clone(),
110      handler: self.handler.clone(),
111      middlewares: ArcSwap::new(self.middlewares.load_full()),
112      has_middleware: AtomicBool::new(self.has_middleware.load(Ordering::Acquire)),
113      tsr: self.tsr,
114      #[cfg(feature = "plugins")]
115      plugins: RwLock::new(Vec::new()),
116      #[cfg(feature = "plugins")]
117      plugins_initialized: AtomicBool::new(true),
118      http_protocol: {
119        let lock = OnceLock::new();
120        if let Some(v) = self.http_protocol.get() {
121          let _ = lock.set(*v);
122        }
123        lock
124      },
125      // Preserve the source route's signal handlers across `cloned_with_path`
126      // (nest/mount): allocating a fresh `SignalArbiter` here used to silently
127      // drop every handler the caller had registered on the original route.
128      #[cfg(feature = "signals")]
129      signals: self.signals.clone(),
130      #[cfg(any(feature = "utoipa", feature = "vespera"))]
131      openapi: RwLock::new(self.openapi.read().clone()),
132      timeout: {
133        let lock = OnceLock::new();
134        if let Some(v) = self.timeout.get() {
135          let _ = lock.set(*v);
136        }
137        lock
138      },
139      simd_json_mode: {
140        let lock = OnceLock::new();
141        if let Some(v) = self.simd_json_mode.get() {
142          let _ = lock.set(*v);
143        }
144        lock
145      },
146    };
147    Arc::new(cloned)
148  }
149}