Skip to main content

httpward_core/core/context/
extensions.rs

1/// Extensions map for storing arbitrary data in HttpWardContext during request lifetime
2/// This allows middleware to share serialized objects without modifying context structure
3
4use std::any::Any;
5use std::sync::Arc;
6use parking_lot::RwLock;
7use std::collections::HashMap;
8
9/// Thread-safe, cloneable storage for arbitrary data keyed by strings
10/// Uses type erasure (Any) to support any Send + Sync type
11#[derive(Clone)]
12pub struct ExtensionsMap {
13    inner: Arc<RwLock<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
14}
15
16impl ExtensionsMap {
17    /// Create a new empty extensions map
18    pub fn new() -> Self {
19        Self {
20            inner: Arc::new(RwLock::new(HashMap::new())),
21        }
22    }
23
24    /// Insert a value into the extensions map
25    ///
26    /// # Arguments
27    /// * `key` - The key to store the value under
28    /// * `value` - The value to store (must be Send + Sync + 'static)
29    ///
30    /// # Example
31    /// ```ignore
32    /// ctx.extensions.insert("user_id", 12345u64);
33    /// ctx.extensions.insert("claims", jwt_claims);
34    /// ```
35    pub fn insert<T: Any + Send + Sync + 'static>(&self, key: impl Into<String>, value: T) {
36        self.inner.write().insert(key.into(), Arc::new(value));
37    }
38
39    /// Retrieve a value from the extensions map
40    ///
41    /// # Arguments
42    /// * `key` - The key to retrieve
43    ///
44    /// # Returns
45    /// * `Some(Arc<T>)` if the key exists and the type matches
46    /// * `None` if the key doesn't exist or type mismatch occurs
47    ///
48    /// # Example
49    /// ```ignore
50    /// if let Some(user_id) = ctx.extensions.get::<u64>("user_id") {
51    ///     println!("User ID: {}", user_id);
52    /// }
53    /// ```
54    pub fn get<T: Any + Send + Sync + 'static>(&self, key: &str) -> Option<Arc<T>> {
55        let inner = self.inner.read();
56        let value = inner.get(key)?.clone();
57        value.downcast::<T>().ok()
58    }
59
60    /// Check if a key exists in the extensions map
61    pub fn contains_key(&self, key: &str) -> bool {
62        self.inner.read().contains_key(key)
63    }
64
65    /// Remove a value from the extensions map
66    pub fn remove(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
67        self.inner.write().remove(key)
68    }
69
70    /// Clear all extensions
71    pub fn clear(&self) {
72        self.inner.write().clear();
73    }
74
75    /// Get the number of stored extensions
76    pub fn len(&self) -> usize {
77        self.inner.read().len()
78    }
79
80    /// Check if the extensions map is empty
81    pub fn is_empty(&self) -> bool {
82        self.inner.read().is_empty()
83    }
84}
85
86impl Default for ExtensionsMap {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl std::fmt::Debug for ExtensionsMap {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        f.debug_struct("ExtensionsMap")
95            .field("count", &self.len())
96            .finish()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_insert_and_get() {
106        let ext = ExtensionsMap::new();
107
108        // Test with u64
109        ext.insert("user_id", 123u64);
110        assert_eq!(ext.get::<u64>("user_id").map(|v| *v), Some(123u64));
111
112        // Test with String
113        ext.insert("session", "abc123".to_string());
114        assert_eq!(ext.get::<String>("session").map(|v| (*v).clone()), Some("abc123".to_string()));
115    }
116
117    #[test]
118    fn test_type_mismatch() {
119        let ext = ExtensionsMap::new();
120        ext.insert("value", 42u64);
121
122        // Type mismatch should return None
123        assert_eq!(ext.get::<String>("value"), None);
124    }
125
126    #[test]
127    fn test_missing_key() {
128        let ext = ExtensionsMap::new();
129        assert_eq!(ext.get::<u64>("nonexistent"), None);
130    }
131
132    #[test]
133    fn test_contains_key() {
134        let ext = ExtensionsMap::new();
135        ext.insert("key1", "value".to_string());
136
137        assert!(ext.contains_key("key1"));
138        assert!(!ext.contains_key("key2"));
139    }
140
141    #[test]
142    fn test_remove() {
143        let ext = ExtensionsMap::new();
144        ext.insert("key", 42u64);
145
146        assert!(ext.remove("key").is_some());
147        assert!(ext.get::<u64>("key").is_none());
148    }
149
150    #[test]
151    fn test_clone_shares_data() {
152        let ext1 = ExtensionsMap::new();
153        ext1.insert("shared", 999u64);
154
155        let ext2 = ext1.clone();
156
157        // Both should see the same data
158        assert_eq!(ext2.get::<u64>("shared").map(|v| *v), Some(999u64));
159    }
160
161    #[test]
162    fn test_multiple_types() {
163        let ext = ExtensionsMap::new();
164
165        ext.insert("int", 42u64);
166        ext.insert("string", "hello".to_string());
167        ext.insert("float", 3.14f64);
168
169        assert_eq!(ext.len(), 3);
170        assert_eq!(ext.get::<u64>("int").map(|v| *v), Some(42u64));
171        assert_eq!(ext.get::<String>("string").map(|v| (*v).clone()), Some("hello".to_string()));
172        assert_eq!(ext.get::<f64>("float").map(|v| *v), Some(3.14f64));
173    }
174}
175