Skip to main content

sova_core/
state.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
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: HashMap<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: HashMap::new(),
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: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
58}
59
60impl Extensions {
61    pub fn new() -> Self {
62        Self {
63            map: HashMap::new(),
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#[derive(Clone)]
92pub struct MatchedMeta(pub crate::route_value::MetaMap);
93
94/// Optional slot filled when a route matches — for root middleware that runs
95/// before match but needs meta after `next` (e.g. SEO head inject).
96#[derive(Clone, Default)]
97pub struct MatchedMetaCapture {
98    inner: std::sync::Arc<std::sync::Mutex<Option<crate::route_value::MetaMap>>>,
99}
100
101impl MatchedMetaCapture {
102    pub fn new() -> Self {
103        Self::default()
104    }
105
106    pub fn set(&self, meta: crate::route_value::MetaMap) {
107        *self.inner.lock().unwrap() = Some(meta);
108    }
109
110    pub fn get(&self) -> Option<crate::route_value::MetaMap> {
111        self.inner.lock().unwrap().clone()
112    }
113}
114
115/// Matched route path template (e.g. `/users/:id`), set after route match.
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct MatchedRoute(pub String);
118
119/// Capture for root middleware: filled when a route matches (low-cardinality metrics).
120#[derive(Clone, Default)]
121pub struct MatchedRouteCapture {
122    inner: std::sync::Arc<std::sync::Mutex<Option<String>>>,
123}
124
125impl MatchedRouteCapture {
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    pub fn set(&self, route: impl Into<String>) {
131        *self.inner.lock().unwrap() = Some(route.into());
132    }
133
134    pub fn get(&self) -> Option<String> {
135        self.inner.lock().unwrap().clone()
136    }
137}