Skip to main content

sova_core/
state.rs

1use rustc_hash::FxHashMap;
2use std::any::{Any, TypeId};
3use std::sync::Arc;
4
5/// Typed bag keyed by [`TypeId`] (route meta, shared app state).
6///
7/// Inserting the same `T` twice keeps the **last** value. Different types never
8/// conflict; call order across types does not matter.
9#[derive(Default, Clone)]
10pub struct TypeMap {
11    map: FxHashMap<TypeId, Arc<dyn Any + Send + Sync>>,
12}
13
14/// Shared application state: `app.state(db)` / `req.state::<Database>()`.
15pub type StateMap = TypeMap;
16
17impl TypeMap {
18    pub fn new() -> Self {
19        Self {
20            map: FxHashMap::default(),
21        }
22    }
23
24    pub fn insert<T>(&mut self, value: T)
25    where
26        T: Send + Sync + 'static,
27    {
28        self.map.insert(TypeId::of::<T>(), Arc::new(value));
29    }
30
31    pub fn get<T>(&self) -> Option<Arc<T>>
32    where
33        T: Send + Sync + 'static,
34    {
35        self.map
36            .get(&TypeId::of::<T>())
37            .and_then(|v| v.clone().downcast::<T>().ok())
38    }
39
40    /// Merge another map; later values overwrite the same TypeId.
41    pub fn extend(&mut self, other: TypeMap) {
42        self.map.extend(other.map);
43    }
44
45    pub fn is_empty(&self) -> bool {
46        self.map.is_empty()
47    }
48
49    pub(crate) fn clone_map(&self) -> TypeMap {
50        self.clone()
51    }
52}
53
54/// Per-request typed bag: `req.set(user)` / `req.get::<User>()`.
55#[derive(Default)]
56pub struct Extensions {
57    map: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
58}
59
60impl Extensions {
61    pub fn new() -> Self {
62        Self {
63            map: FxHashMap::default(),
64        }
65    }
66
67    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
68        self.map.insert(TypeId::of::<T>(), Box::new(value));
69    }
70
71    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
72        self.map
73            .get(&TypeId::of::<T>())
74            .and_then(|v| v.downcast_ref::<T>())
75    }
76
77    pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
78        self.map
79            .get_mut(&TypeId::of::<T>())
80            .and_then(|v| v.downcast_mut::<T>())
81    }
82
83    pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
84        self.map
85            .remove(&TypeId::of::<T>())
86            .and_then(|v| v.downcast::<T>().ok().map(|b| *b))
87    }
88}
89
90/// Route metadata bag attached to the request after a successful match.
91///
92/// Wrapped in [`Arc`] so inject-on-match does not deep-clone the map per request.
93#[derive(Clone)]
94pub struct MatchedMeta(pub Arc<crate::route_value::MetaMap>);
95
96/// Optional slot filled when a route matches — for root middleware that runs
97/// before match but needs meta after `next` (e.g. SEO head inject).
98#[derive(Clone, Default)]
99pub struct MatchedMetaCapture {
100    inner: std::sync::Arc<std::sync::Mutex<Option<Arc<crate::route_value::MetaMap>>>>,
101}
102
103impl MatchedMetaCapture {
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    pub fn set(&self, meta: Arc<crate::route_value::MetaMap>) {
109        *self.inner.lock().unwrap() = Some(meta);
110    }
111
112    pub fn get(&self) -> Option<Arc<crate::route_value::MetaMap>> {
113        self.inner.lock().unwrap().clone()
114    }
115}
116
117/// Matched route path template (e.g. `/users/:id`), set after route match.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct MatchedRoute(pub Arc<str>);
120
121/// Capture for root middleware: filled when a route matches (low-cardinality metrics).
122#[derive(Clone, Default)]
123pub struct MatchedRouteCapture {
124    inner: std::sync::Arc<std::sync::Mutex<Option<Arc<str>>>>,
125}
126
127impl MatchedRouteCapture {
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    pub fn set(&self, route: Arc<str>) {
133        *self.inner.lock().unwrap() = Some(route);
134    }
135
136    pub fn get(&self) -> Option<Arc<str>> {
137        self.inner.lock().unwrap().clone()
138    }
139}