Skip to main content

armature_core/
extensions.rs

1//! Type-safe request extensions for zero-cost state extraction.
2//!
3//! This module provides a way to attach typed data to requests without
4//! runtime type checking overhead. Unlike the DI container which uses
5//! `Any` downcasting, extensions use a type-erased map that only requires
6//! type checks at the point of insertion, not retrieval.
7//!
8//! # Performance
9//!
10//! - **Insertion**: a linear scan over at most a handful of `TypeId`s
11//! - **Retrieval**: the same scan, then a pointer cast (no runtime type check)
12//! - **Memory**: a `SmallVec` with eight inline slots and one `Arc<T>` per
13//!   extension type, so a realistic request never allocates a table
14//!
15//! # Example
16//!
17//! ```rust,ignore
18//! use armature_core::{Extensions, State};
19//!
20//! // Application state
21//! #[derive(Clone)]
22//! struct AppState {
23//!     db_pool: Pool,
24//! }
25//!
26//! // Insert state at startup
27//! let mut extensions = Extensions::new();
28//! extensions.insert(AppState { db_pool });
29//!
30//! // Extract in handler (zero-cost after setup)
31//! async fn handler(state: State<AppState>) -> Result<HttpResponse, Error> {
32//!     let pool = &state.db_pool;
33//!     // ...
34//! }
35//! ```
36
37use smallvec::SmallVec;
38use std::any::{Any, TypeId};
39use std::sync::Arc;
40
41/// Eight inline slots. A linear scan comparing `TypeId`s — one `u128` compare
42/// each — beats hashing one, and it never allocates for a realistic request.
43type Slots = SmallVec<[(TypeId, Arc<dyn Any + Send + Sync>); 8]>;
44
45/// Type-safe extensions container.
46///
47/// Stores typed values keyed by `TypeId`, found by scanning rather than
48/// hashing, with no runtime type checking after insertion.
49#[derive(Clone, Default)]
50pub struct Extensions {
51    slots: Slots,
52}
53
54impl Extensions {
55    /// Create a new empty extensions container.
56    #[inline]
57    pub fn new() -> Self {
58        Self {
59            slots: Slots::new(),
60        }
61    }
62
63    /// Create with pre-allocated capacity.
64    #[inline]
65    pub fn with_capacity(capacity: usize) -> Self {
66        Self {
67            slots: Slots::with_capacity(capacity),
68        }
69    }
70
71    /// Whether the storage has spilled to the heap.
72    #[inline]
73    pub fn spilled(&self) -> bool {
74        self.slots.spilled()
75    }
76
77    /// Insert a typed value into the extensions.
78    ///
79    /// If a value of this type already exists, it is replaced.
80    ///
81    /// # Example
82    ///
83    /// ```rust
84    /// use armature_core::Extensions;
85    ///
86    /// let mut ext = Extensions::new();
87    /// ext.insert(42i32);
88    /// ext.insert("hello");
89    /// ```
90    #[inline]
91    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
92        self.insert_arc(Arc::new(value));
93    }
94
95    /// Insert an Arc-wrapped value directly.
96    ///
97    /// This is more efficient when you already have an Arc.
98    #[inline]
99    pub fn insert_arc<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
100        let type_id = TypeId::of::<T>();
101        let erased: Arc<dyn Any + Send + Sync> = value;
102        if let Some(slot) = self.slots.iter_mut().find(|(k, _)| *k == type_id) {
103            slot.1 = erased;
104            return;
105        }
106        self.slots.push((type_id, erased));
107    }
108
109    /// Get a reference to a typed value.
110    ///
111    /// Returns `None` if no value of this type exists.
112    ///
113    /// # Performance
114    ///
115    /// A scan over at most a handful of `TypeId`s followed by a pointer cast
116    /// (no runtime type checking).
117    ///
118    /// # Example
119    ///
120    /// ```rust
121    /// use armature_core::Extensions;
122    ///
123    /// let mut ext = Extensions::new();
124    /// ext.insert(42i32);
125    ///
126    /// assert_eq!(ext.get::<i32>(), Some(&42));
127    /// assert_eq!(ext.get::<String>(), None);
128    /// ```
129    #[inline]
130    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
131        let type_id = TypeId::of::<T>();
132        self.slots
133            .iter()
134            .find(|(k, _)| *k == type_id)
135            .and_then(|(_, v)| v.downcast_ref::<T>())
136    }
137
138    /// Get an Arc reference to a typed value.
139    ///
140    /// This is useful when you need to clone the Arc for async operations.
141    #[inline]
142    pub fn get_arc<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
143        let type_id = TypeId::of::<T>();
144        self.slots
145            .iter()
146            .find(|(k, _)| *k == type_id)
147            .and_then(|(_, v)| v.clone().downcast::<T>().ok())
148    }
149
150    /// Check if a value of this type exists.
151    #[inline]
152    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
153        let type_id = TypeId::of::<T>();
154        self.slots.iter().any(|(k, _)| *k == type_id)
155    }
156
157    /// Remove a typed value from the extensions.
158    ///
159    /// Returns true if the value existed and was removed.
160    #[inline]
161    pub fn remove<T: Send + Sync + 'static>(&mut self) -> bool {
162        let type_id = TypeId::of::<T>();
163        match self.slots.iter().position(|(k, _)| *k == type_id) {
164            Some(i) => {
165                self.slots.remove(i);
166                true
167            }
168            None => false,
169        }
170    }
171
172    /// Clear all extensions.
173    #[inline]
174    pub fn clear(&mut self) {
175        self.slots.clear();
176    }
177
178    /// Get the number of extensions.
179    #[inline]
180    pub fn len(&self) -> usize {
181        self.slots.len()
182    }
183
184    /// Check if extensions is empty.
185    #[inline]
186    pub fn is_empty(&self) -> bool {
187        self.slots.is_empty()
188    }
189
190    /// Merge another extensions container into this one.
191    ///
192    /// Values from `other` will overwrite values in `self` for the same type.
193    pub fn extend(&mut self, other: Extensions) {
194        for (id, value) in other.slots {
195            if let Some(slot) = self.slots.iter_mut().find(|(k, _)| *k == id) {
196                slot.1 = value;
197            } else {
198                self.slots.push((id, value));
199            }
200        }
201    }
202}
203
204impl std::fmt::Debug for Extensions {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        f.debug_struct("Extensions")
207            .field("count", &self.slots.len())
208            .finish()
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_insert_and_get() {
218        let mut ext = Extensions::new();
219
220        ext.insert(42i32);
221        ext.insert("hello".to_string());
222
223        assert_eq!(ext.get::<i32>(), Some(&42));
224        assert_eq!(ext.get::<String>(), Some(&"hello".to_string()));
225        assert_eq!(ext.get::<f64>(), None);
226    }
227
228    #[test]
229    fn test_insert_replaces() {
230        let mut ext = Extensions::new();
231
232        ext.insert(42i32);
233        ext.insert(100i32);
234
235        assert_eq!(ext.get::<i32>(), Some(&100));
236    }
237
238    #[test]
239    fn test_contains() {
240        let mut ext = Extensions::new();
241
242        assert!(!ext.contains::<i32>());
243        ext.insert(42i32);
244        assert!(ext.contains::<i32>());
245    }
246
247    #[test]
248    fn test_remove() {
249        let mut ext = Extensions::new();
250        ext.insert(42i32);
251
252        let removed = ext.remove::<i32>();
253        assert!(removed);
254        assert!(!ext.contains::<i32>());
255    }
256
257    #[test]
258    fn test_arc_insert() {
259        let mut ext = Extensions::new();
260        let arc = Arc::new(42i32);
261
262        ext.insert_arc(arc.clone());
263
264        let retrieved = ext.get_arc::<i32>().unwrap();
265        assert_eq!(*retrieved, 42);
266    }
267
268    #[test]
269    fn eight_extensions_stay_inline() {
270        let mut ext = Extensions::new();
271        ext.insert(1u8);
272        ext.insert(2u16);
273        ext.insert(3u32);
274        ext.insert(4u64);
275        ext.insert(5i8);
276        ext.insert(6i16);
277        ext.insert(7i32);
278        ext.insert(8i64);
279        assert_eq!(ext.len(), 8);
280        assert!(!ext.spilled(), "eight extensions must not allocate a table");
281        assert_eq!(ext.get::<u32>(), Some(&3u32));
282    }
283
284    #[test]
285    fn insert_replaces_the_same_type() {
286        let mut ext = Extensions::new();
287        ext.insert(1u32);
288        ext.insert(2u32);
289        assert_eq!(ext.len(), 1);
290        assert_eq!(ext.get::<u32>(), Some(&2u32));
291    }
292
293    #[test]
294    fn extend_overwrites_colliding_types_and_keeps_the_rest() {
295        let mut a = Extensions::new();
296        a.insert(1u32);
297        a.insert("keep");
298        let mut b = Extensions::new();
299        b.insert(2u32);
300        a.extend(b);
301        assert_eq!(a.get::<u32>(), Some(&2u32));
302        assert_eq!(a.get::<&str>(), Some(&"keep"));
303    }
304
305    #[test]
306    fn test_clone() {
307        let mut ext = Extensions::new();
308        ext.insert(42i32);
309
310        let cloned = ext.clone();
311        assert_eq!(cloned.get::<i32>(), Some(&42));
312    }
313}