Skip to main content

evento_core/
context.rs

1//! Type-safe context for storing request-scoped data.
2//!
3//! This module provides type-erased containers for storing arbitrary data
4//! during event processing. Values are stored and retrieved by their Rust type.
5//!
6//! # Types
7//!
8//! - [`Context`] - Single-threaded type map (not `Send`/`Sync`)
9//! - [`RwContext`] - Thread-safe version wrapped in `Arc<RwLock<_>>`
10//! - [`Data`] - Arc-wrapped shared data for cloneable access
11//!
12//! # Example
13//!
14//! ```rust,ignore
15//! use evento::context::{RwContext, Data};
16//!
17//! // Create a context
18//! let ctx = RwContext::new();
19//!
20//! // Store data by type
21//! ctx.insert(Data::new(MyAppState { ... }));
22//! ctx.insert(42u32);
23//!
24//! // Retrieve data by type
25//! let state: Data<MyAppState> = ctx.extract();
26//! let number: u32 = ctx.get().unwrap();
27//! ```
28
29use serde::Serialize;
30use std::{
31    any::{type_name, Any, TypeId},
32    collections::HashMap,
33    fmt,
34    hash::{BuildHasherDefault, Hasher},
35    ops::Deref,
36    sync::{Arc, RwLock},
37};
38
39/// A hasher for `TypeId`s that takes advantage of its known characteristics.
40///
41/// Author of `anymap` crate has done research on the topic:
42/// https://github.com/chris-morgan/anymap/blob/2e9a5704/src/lib.rs#L599
43#[derive(Debug, Default)]
44struct NoOpHasher(u64);
45
46impl Hasher for NoOpHasher {
47    fn write(&mut self, _bytes: &[u8]) {
48        unimplemented!("This NoOpHasher can only handle u64s")
49    }
50
51    fn write_u64(&mut self, i: u64) {
52        self.0 = i;
53    }
54
55    fn finish(&self) -> u64 {
56        self.0
57    }
58}
59
60/// A type map for storing request-scoped data.
61///
62/// `Context` stores values by their Rust type, allowing type-safe retrieval.
63/// All entries must be owned types that are `Send + Sync + 'static`.
64///
65/// For thread-safe access, use [`RwContext`] instead.
66///
67/// # Example
68///
69/// ```rust,ignore
70/// let mut ctx = Context::new();
71/// ctx.insert(42u32);
72/// ctx.insert("hello".to_string());
73///
74/// assert_eq!(ctx.get::<u32>(), Some(&42));
75/// assert_eq!(ctx.get::<String>(), Some(&"hello".to_string()));
76/// ```
77#[derive(Default)]
78pub struct Context {
79    map: HashMap<TypeId, Box<dyn Any + Send + Sync>, BuildHasherDefault<NoOpHasher>>,
80}
81
82impl Context {
83    /// Creates an empty `Context`.
84    #[inline]
85    pub fn new() -> Context {
86        Context {
87            map: HashMap::default(),
88        }
89    }
90
91    /// Insert an item into the map.
92    ///
93    /// If an item of this type was already stored, it will be replaced and returned.
94    pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
95        self.map
96            .insert(TypeId::of::<T>(), Box::new(val))
97            .and_then(downcast_owned)
98    }
99
100    /// Check if map contains an item of a given type.
101    pub fn contains<T: 'static>(&self) -> bool {
102        self.map.contains_key(&TypeId::of::<T>())
103    }
104
105    /// Get a reference to an item of a given type.
106    pub fn extract<T: 'static>(&self) -> &T {
107        match self.get::<T>() {
108            Some(v) => v,
109            _ => {
110                tracing::debug!(
111                    "Failed to extract `Data<{}>` For the Data extractor to work \
112        correctly, wrap the data with `Data::new()` and pass it to `evento::data()`. \
113        Ensure that types align in both the set and retrieve calls.",
114                    type_name::<T>()
115                );
116
117                panic!(
118                    "Requested application data is not configured correctly. \
119    View/enable debug logs for more details."
120                );
121            }
122        }
123    }
124
125    /// Get a reference to an item of a given type.
126    pub fn get<T: 'static>(&self) -> Option<&T> {
127        self.map
128            .get(&TypeId::of::<T>())
129            .and_then(|boxed| boxed.downcast_ref())
130    }
131
132    /// Get a mutable reference to an item of a given type.
133    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
134        self.map
135            .get_mut(&TypeId::of::<T>())
136            .and_then(|boxed| boxed.downcast_mut())
137    }
138
139    /// Remove an item from the map of a given type.
140    ///
141    /// If an item of this type was already stored, it will be returned.
142    pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
143        self.map.remove(&TypeId::of::<T>()).and_then(downcast_owned)
144    }
145
146    /// Clear the `Context` of all inserted extensions.
147    #[inline]
148    pub fn clear(&mut self) {
149        self.map.clear();
150    }
151
152    /// Extends self with the items from another `Context`.
153    pub fn extend(&mut self, other: Context) {
154        self.map.extend(other.map);
155    }
156}
157
158impl fmt::Debug for Context {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        f.debug_struct("Context").finish()
161    }
162}
163
164fn downcast_owned<T: Send + Sync + 'static>(boxed: Box<dyn Any + Send + Sync>) -> Option<T> {
165    boxed.downcast().ok().map(|boxed| *boxed)
166}
167
168/// Arc-wrapped shared data for use in contexts.
169///
170/// `Data<T>` wraps a value in an `Arc` for cheap cloning and sharing
171/// across async tasks. It implements `Deref` for transparent access.
172///
173/// # Example
174///
175/// ```rust,ignore
176/// use evento::context::Data;
177///
178/// struct AppConfig {
179///     database_url: String,
180/// }
181///
182/// let config = Data::new(AppConfig {
183///     database_url: "postgres://...".into(),
184/// });
185///
186/// // Clone is cheap (just Arc clone)
187/// let config2 = config.clone();
188///
189/// // Access inner value via Deref
190/// println!("{}", config.database_url);
191/// ```
192#[derive(Debug)]
193pub struct Data<T: ?Sized>(Arc<T>);
194
195impl<T> Data<T> {
196    /// Create new `Data` instance wrapping the value in an `Arc`.
197    pub fn new(state: T) -> Data<T> {
198        Data(Arc::new(state))
199    }
200}
201
202impl<T: ?Sized> Data<T> {
203    /// Returns reference to inner `T`.
204    pub fn get_ref(&self) -> &T {
205        self.0.as_ref()
206    }
207
208    /// Unwraps to the internal `Arc<T>`
209    pub fn into_inner(self) -> Arc<T> {
210        self.0
211    }
212}
213
214impl<T: ?Sized> Deref for Data<T> {
215    type Target = Arc<T>;
216
217    fn deref(&self) -> &Arc<T> {
218        &self.0
219    }
220}
221
222impl<T: ?Sized> Clone for Data<T> {
223    fn clone(&self) -> Data<T> {
224        Data(Arc::clone(&self.0))
225    }
226}
227
228impl<T: ?Sized> From<Arc<T>> for Data<T> {
229    fn from(arc: Arc<T>) -> Self {
230        Data(arc)
231    }
232}
233
234impl<T> Serialize for Data<T>
235where
236    T: Serialize,
237{
238    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239    where
240        S: serde::Serializer,
241    {
242        self.0.serialize(serializer)
243    }
244}
245
246/// Thread-safe context for storing request-scoped data.
247///
248/// `RwContext` wraps a [`Context`] in `Arc<RwLock<_>>` for safe concurrent access.
249/// It can be cloned cheaply and shared across async tasks.
250///
251/// # Example
252///
253/// ```rust,ignore
254/// use evento::context::RwContext;
255///
256/// let ctx = RwContext::new();
257///
258/// // Insert data (acquires write lock)
259/// ctx.insert(42u32);
260///
261/// // Get data (acquires read lock, clones the value)
262/// let value: Option<u32> = ctx.get();
263///
264/// // Extract panics if not found (useful for required dependencies)
265/// let value: u32 = ctx.extract();
266/// ```
267///
268/// # Panics
269///
270/// Methods will panic if the internal `RwLock` is poisoned.
271pub struct RwContext(Arc<RwLock<Context>>);
272
273impl Default for RwContext {
274    fn default() -> Self {
275        Self::new()
276    }
277}
278
279impl RwContext {
280    /// Creates an empty `RwContext`.
281    #[inline]
282    pub fn new() -> Self {
283        RwContext(Arc::new(RwLock::new(Context::new())))
284    }
285
286    /// Insert an item into the map.
287    ///
288    /// If an item of this type was already stored, it will be replaced and returned.
289    pub fn insert<T: Send + Sync + 'static>(&self, val: T) -> Option<T> {
290        self.0.write().expect("RwContext lock poisoned").insert(val)
291    }
292
293    /// Check if map contains an item of a given type.
294    pub fn contains<T: 'static>(&self) -> bool {
295        self.0
296            .read()
297            .expect("RwContext lock poisoned")
298            .contains::<T>()
299    }
300
301    /// Get a clone of an item of a given type, panics if not found.
302    pub fn extract<T: Clone + 'static>(&self) -> T {
303        self.0
304            .read()
305            .expect("RwContext lock poisoned")
306            .extract::<T>()
307            .clone()
308    }
309
310    /// Get a clone of an item of a given type.
311    pub fn get<T: Clone + 'static>(&self) -> Option<T> {
312        self.0
313            .read()
314            .expect("RwContext lock poisoned")
315            .get::<T>()
316            .cloned()
317    }
318
319    /// Remove an item from the map of a given type.
320    ///
321    /// If an item of this type was already stored, it will be returned.
322    pub fn remove<T: Send + Sync + 'static>(&self) -> Option<T> {
323        self.0
324            .write()
325            .expect("RwContext lock poisoned")
326            .remove::<T>()
327    }
328
329    /// Clear the `RwContext` of all inserted extensions.
330    #[inline]
331    pub fn clear(&self) {
332        self.0.write().expect("RwContext lock poisoned").clear();
333    }
334
335    /// Extends self with the items from another `Context`.
336    pub fn extend(&self, other: Context) {
337        self.0
338            .write()
339            .expect("RwContext lock poisoned")
340            .extend(other);
341    }
342}
343
344impl Clone for RwContext {
345    fn clone(&self) -> Self {
346        RwContext(Arc::clone(&self.0))
347    }
348}