Skip to main content

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}