tako_rs_core/router/state.rs
1//! Per-router typed state and signal-arbiter accessors.
2
3use std::sync::Arc;
4use std::sync::atomic::Ordering;
5
6use super::Router;
7use crate::router_state::RouterState;
8#[cfg(feature = "signals")]
9use crate::signals::Signal;
10#[cfg(feature = "signals")]
11use crate::signals::SignalArbiter;
12use crate::state::set_state;
13
14impl Router {
15 /// Adds a value to the global type-based state accessible by all handlers.
16 ///
17 /// Global state allows sharing data across different routes and middleware.
18 /// Values are stored by their concrete type and retrieved via the
19 /// `State` extractor (from `tako-extractors`) or with
20 /// [`crate::state::get_state`].
21 ///
22 /// # Examples
23 ///
24 /// ```rust
25 /// use tako::router::Router;
26 ///
27 /// #[derive(Clone)]
28 /// struct AppConfig { database_url: String, api_key: String }
29 ///
30 /// let mut router = Router::new();
31 /// router.state(AppConfig {
32 /// database_url: "postgresql://localhost/mydb".to_string(),
33 /// api_key: "secret-key".to_string(),
34 /// });
35 /// // You can also store simple types by type:
36 /// router.state::<String>("1.0.0".to_string());
37 /// ```
38 pub fn state<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
39 set_state(value);
40 }
41
42 /// Inserts a value into this router's instance-local typed state.
43 ///
44 /// Unlike [`Router::state`] (which writes the process-global registry and
45 /// therefore allows only one value per `T` per process), `with_state` is
46 /// per-router — multiple routers can hold distinct `T`s without collisions.
47 ///
48 /// The `State` extractor (from `tako-extractors`) reads the per-router
49 /// store first and falls back to the global store if no per-router value
50 /// exists, so existing code that uses `set_state` / `Router::state`
51 /// continues to work unchanged.
52 ///
53 /// Hot-path cost is one `Arc` clone per request *only when* at least one
54 /// `with_state` call has happened on this router; an `AtomicBool::Acquire`
55 /// fast-path skips it for routers that don't use instance-local state.
56 ///
57 /// # Examples
58 ///
59 /// ```rust
60 /// use tako::router::Router;
61 ///
62 /// #[derive(Clone)]
63 /// struct Db;
64 ///
65 /// let mut router = Router::new();
66 /// router.with_state(Db);
67 /// ```
68 pub fn with_state<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> &mut Self {
69 self.router_state.insert(value);
70 self.has_router_state.store(true, Ordering::Release);
71 self
72 }
73
74 /// Returns the per-router typed state (shared `Arc`).
75 #[inline]
76 pub fn router_state(&self) -> &Arc<RouterState> {
77 &self.router_state
78 }
79
80 #[cfg(feature = "signals")]
81 /// Returns a reference to the signal arbiter.
82 pub fn signals(&self) -> &SignalArbiter {
83 &self.signals
84 }
85
86 #[cfg(feature = "signals")]
87 /// Returns a clone of the signal arbiter, useful for sharing through state.
88 pub fn signal_arbiter(&self) -> SignalArbiter {
89 self.signals.clone()
90 }
91
92 #[cfg(feature = "signals")]
93 /// Registers a handler for a named signal on this router's arbiter.
94 pub fn on_signal<F, Fut>(&self, id: impl Into<String>, handler: F)
95 where
96 F: Fn(Signal) -> Fut + Send + Sync + 'static,
97 Fut: std::future::Future<Output = ()> + Send + 'static,
98 {
99 self.signals.on(id, handler);
100 }
101
102 #[cfg(feature = "signals")]
103 /// Emits a signal through this router's arbiter.
104 pub async fn emit_signal(&self, signal: Signal) {
105 self.signals.emit(signal).await;
106 }
107}