Skip to main content

cognis_core/
extensions.rs

1//! A typed key-value container, keyed by `TypeId`. Inspired by
2//! `tower::Extensions` / `axum::Extension`. Use this when a struct needs
3//! to carry arbitrary plugin-supplied data without baking those types into
4//! its public surface.
5
6use std::any::{Any, TypeId};
7use std::collections::HashMap;
8use std::fmt;
9
10/// A typed map indexed by `TypeId`. Each `T: Any + Send + Sync + 'static`
11/// can have at most one value stored.
12#[derive(Default)]
13pub struct Extensions {
14    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
15}
16
17impl Extensions {
18    /// Create an empty `Extensions`.
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Insert a value. Returns the previous value of the same type, if any.
24    pub fn insert<T: Any + Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
25        self.map
26            .insert(TypeId::of::<T>(), Box::new(value))
27            .and_then(|prev| prev.downcast::<T>().ok().map(|b| *b))
28    }
29
30    /// Get a reference to a stored value of type `T`.
31    pub fn get<T: Any + Send + Sync + 'static>(&self) -> Option<&T> {
32        self.map
33            .get(&TypeId::of::<T>())
34            .and_then(|b| b.downcast_ref::<T>())
35    }
36
37    /// Get a mutable reference to a stored value of type `T`.
38    pub fn get_mut<T: Any + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
39        self.map
40            .get_mut(&TypeId::of::<T>())
41            .and_then(|b| b.downcast_mut::<T>())
42    }
43
44    /// Remove and return the stored value of type `T`.
45    pub fn remove<T: Any + Send + Sync + 'static>(&mut self) -> Option<T> {
46        self.map
47            .remove(&TypeId::of::<T>())
48            .and_then(|b| b.downcast::<T>().ok().map(|b| *b))
49    }
50
51    /// True if a value of type `T` is stored.
52    pub fn contains<T: Any + Send + Sync + 'static>(&self) -> bool {
53        self.map.contains_key(&TypeId::of::<T>())
54    }
55
56    /// Number of distinct types stored.
57    pub fn len(&self) -> usize {
58        self.map.len()
59    }
60
61    /// True if no values are stored.
62    pub fn is_empty(&self) -> bool {
63        self.map.is_empty()
64    }
65
66    /// Clear all stored values.
67    pub fn clear(&mut self) {
68        self.map.clear();
69    }
70}
71
72impl fmt::Debug for Extensions {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.debug_struct("Extensions")
75            .field("len", &self.map.len())
76            .finish()
77    }
78}
79
80impl Clone for Extensions {
81    /// Clones the *map*, but NOT the values inside. Cloning here makes a
82    /// fresh empty `Extensions` — there's no general way to clone arbitrary
83    /// `dyn Any`. If you need cloned values, store `Arc<T>` instead of `T`.
84    fn clone(&self) -> Self {
85        Self::default()
86    }
87}
88
89impl serde::Serialize for Extensions {
90    /// Serializes as an empty JSON object. Extensions contains type-erased
91    /// runtime data that cannot be generically serialized; the empty-object
92    /// representation satisfies `S: Serialize` bounds without losing any
93    /// recoverable information.
94    fn serialize<Se: serde::Serializer>(&self, s: Se) -> std::result::Result<Se::Ok, Se::Error> {
95        use serde::ser::SerializeMap;
96        s.serialize_map(Some(0))?.end()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[derive(Debug, PartialEq)]
105    struct UserId(u32);
106
107    #[derive(Debug, PartialEq)]
108    struct Tag(&'static str);
109
110    #[test]
111    fn insert_get_remove() {
112        let mut ex = Extensions::new();
113        assert!(ex.is_empty());
114
115        assert!(ex.insert(UserId(42)).is_none());
116        assert_eq!(ex.get::<UserId>(), Some(&UserId(42)));
117        assert_eq!(ex.len(), 1);
118
119        let prev = ex.insert(UserId(99));
120        assert_eq!(prev, Some(UserId(42)));
121        assert_eq!(ex.get::<UserId>(), Some(&UserId(99)));
122
123        let removed = ex.remove::<UserId>();
124        assert_eq!(removed, Some(UserId(99)));
125        assert!(ex.get::<UserId>().is_none());
126    }
127
128    #[test]
129    fn distinct_types_coexist() {
130        let mut ex = Extensions::new();
131        ex.insert(UserId(1));
132        ex.insert(Tag("admin"));
133        assert_eq!(ex.get::<UserId>(), Some(&UserId(1)));
134        assert_eq!(ex.get::<Tag>(), Some(&Tag("admin")));
135        assert_eq!(ex.len(), 2);
136    }
137
138    #[test]
139    fn clone_yields_empty() {
140        let mut ex = Extensions::new();
141        ex.insert(UserId(1));
142        let cloned = ex.clone();
143        assert!(cloned.is_empty());
144    }
145}