churust_core/state.rs
1//! Type-keyed shared application state (a minimal DI registry).
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7/// A type-keyed registry holding at most one shared value per type — a minimal
8/// dependency-injection container.
9///
10/// Each value is keyed by its [`TypeId`] and stored behind an [`Arc`], so reads
11/// hand out cheap shared handles. This backs [`AppBuilder::state`] and the
12/// [`State`](crate::State) extractor; you rarely construct one directly.
13///
14/// ```
15/// use churust_core::StateMap;
16///
17/// let mut state = StateMap::new();
18/// state.insert(42u32);
19/// state.insert(String::from("hi"));
20/// assert_eq!(*state.get::<u32>().unwrap(), 42);
21/// assert_eq!(state.get::<String>().unwrap().as_str(), "hi");
22/// assert!(state.get::<bool>().is_none());
23/// ```
24///
25/// [`AppBuilder::state`]: crate::AppBuilder::state
26#[derive(Clone, Default)]
27pub struct StateMap {
28 map: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
29}
30
31impl StateMap {
32 /// Create an empty registry. Equivalent to [`StateMap::default`].
33 ///
34 /// ```
35 /// use churust_core::StateMap;
36 /// assert!(StateMap::new().get::<u32>().is_none());
37 /// ```
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 /// Insert (or replace) the single value held for type `T`.
43 ///
44 /// ```
45 /// use churust_core::StateMap;
46 /// let mut state = StateMap::new();
47 /// state.insert(1u8);
48 /// state.insert(2u8); // replaces the previous u8
49 /// assert_eq!(*state.get::<u8>().unwrap(), 2);
50 /// ```
51 pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
52 self.map.insert(TypeId::of::<T>(), Arc::new(value));
53 }
54
55 /// Get a shared [`Arc`] handle to the value for type `T`, or `None` if no
56 /// value of that type has been inserted.
57 pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
58 self.map
59 .get(&TypeId::of::<T>())
60 .and_then(|arc| arc.clone().downcast::<T>().ok())
61 }
62}
63
64impl std::fmt::Debug for StateMap {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("StateMap")
67 .field("len", &self.map.len())
68 .finish()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn stores_and_retrieves_by_type() {
78 let mut m = StateMap::new();
79 m.insert(7u32);
80 m.insert(String::from("hello"));
81 assert_eq!(*m.get::<u32>().unwrap(), 7);
82 assert_eq!(m.get::<String>().unwrap().as_str(), "hello");
83 assert!(m.get::<i64>().is_none());
84 }
85}