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}