1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Plugin registration/initialization, `OpenAPI` collection, and route-index GC.
#[cfg(any(feature = "utoipa", feature = "vespera"))]
use http::Method;
use super::Router;
#[cfg(feature = "plugins")]
use crate::plugins::TakoPlugin;
impl Router {
/// Registers a plugin with the router.
///
/// Plugins extend the router's functionality by providing additional features
/// like compression, CORS handling, rate limiting, or custom behavior. Plugins
/// are initialized once when the server starts.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "plugins")]
/// use tako::{router::Router, plugins::TakoPlugin};
/// # #[cfg(feature = "plugins")]
/// use anyhow::Result;
///
/// # #[cfg(feature = "plugins")]
/// struct LoggingPlugin;
///
/// # #[cfg(feature = "plugins")]
/// impl TakoPlugin for LoggingPlugin {
/// fn name(&self) -> &'static str {
/// "logging"
/// }
///
/// fn setup(&self, _router: &Router) -> Result<()> {
/// println!("Logging plugin initialized");
/// Ok(())
/// }
/// }
///
/// # #[cfg(feature = "plugins")]
/// # fn example() {
/// let mut router = Router::new();
/// router.plugin(LoggingPlugin);
/// # }
/// ```
#[cfg(feature = "plugins")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
pub fn plugin<P>(&mut self, plugin: P) -> &mut Self
where
P: TakoPlugin + Clone + Send + Sync + 'static,
{
self.plugins.push(Box::new(plugin));
self
}
/// Returns references to all registered plugins.
#[cfg(feature = "plugins")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
pub(crate) fn plugins(&self) -> Vec<&dyn TakoPlugin> {
self.plugins.iter().map(AsRef::as_ref).collect()
}
/// Initializes all registered plugins exactly once.
#[cfg(feature = "plugins")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugins")))]
#[doc(hidden)]
pub fn setup_plugins_once(&self) {
use std::sync::atomic::Ordering;
// Hot-path fast exit: see `Route::setup_plugins_once`. Acquire-load
// pairs with the Release half of the swap so plugin-published state
// is visible by the time we skip the RMW.
if self.plugins_initialized.load(Ordering::Acquire) {
return;
}
if !self.plugins_initialized.swap(true, Ordering::SeqCst) {
for plugin in self.plugins() {
// Surface plugin setup errors loudly — a silently-skipped CORS,
// auth, rate-limit, or CSRF plugin would leave the server
// running without the protection the operator expected
// (security-relevant fail-open). Cold path — first dispatch only.
if let Err(e) = plugin.setup(self) {
tracing::error!(
plugin = plugin.name(),
error = %e,
"router-level TakoPlugin::setup failed; plugin not active"
);
}
}
}
}
/// Collects `OpenAPI` metadata from all registered routes.
///
/// Returns a vector of tuples containing the HTTP method, path, and `OpenAPI`
/// metadata for each route that has `OpenAPI` information attached.
///
/// # Examples
///
/// ```rust,ignore
/// use tako::{router::Router, Method};
///
/// let mut router = Router::new();
/// router.route(Method::GET, "/users", list_users)
/// .summary("List users")
/// .tag("users");
///
/// for (method, path, openapi) in router.collect_openapi_routes() {
/// println!("{} {} - {:?}", method, path, openapi.summary);
/// }
/// ```
#[cfg(any(feature = "utoipa", feature = "vespera"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "utoipa", feature = "vespera"))))]
pub fn collect_openapi_routes(&self) -> Vec<(Method, String, crate::openapi::RouteOpenApi)> {
let mut result = Vec::new();
for (method, weak_vec) in self.routes.iter() {
for weak in weak_vec {
if let Some(route) = weak.upgrade()
&& let Some(openapi) = route.openapi_metadata()
{
result.push((method.clone(), route.path.clone(), openapi));
}
}
}
result
}
/// Drops dangling `Weak<Route>` entries from the per-method `routes` index.
///
/// All current routes stay live for the router's lifetime, so this is a
/// no-op in well-behaved code. It exists as a safety valve: if any future
/// API ever removes from `inner` (hot reload, route deregistration), or if
/// downstream code holds the `Arc<Route>` returned from [`Router::route`]
/// past the router's lifetime, this method bounds the size of the index.
///
/// Cold path; safe to call repeatedly. Linear in the total number of
/// registered routes.
pub fn compact_routes(&mut self) {
for weak_vec in self.routes.iter_mut() {
weak_vec.retain(|w| w.strong_count() > 0);
}
}
}