es_entity/context/with_event_context.rs
1use pin_project::pin_project;
2
3use std::{
4 future::Future,
5 pin::Pin,
6 task::{Context, Poll},
7};
8
9use super::{ContextData, EventContext};
10
11/// Extension trait for propagating event context across async boundaries.
12///
13/// This trait is automatically implemented for all `Future` types and provides
14/// the `with_event_context` method to carry context data across async operations
15/// like `tokio::spawn`, `tokio::task::yield_now()`, and other async boundaries
16/// where thread-local storage is not preserved.
17///
18/// # Examples
19///
20/// ```rust
21/// use es_entity::context::{EventContext, WithEventContext};
22///
23/// async fn example() {
24/// let mut ctx = EventContext::current();
25/// ctx.insert("request_id", &"abc123").unwrap();
26///
27/// let data = ctx.data();
28/// tokio::spawn(async {
29/// // Context is available here
30/// let current = EventContext::current();
31/// // current now has the request_id from the parent
32/// }.with_event_context(data)).await.unwrap();
33/// }
34/// ```
35pub trait WithEventContext: Future {
36 /// Wraps this future with event context data.
37 ///
38 /// This method ensures that when the future is polled, the provided context
39 /// data will be available as the current event context. This is essential
40 /// for maintaining context across async boundaries where the original
41 /// thread-local context is not available.
42 ///
43 /// # Arguments
44 ///
45 /// * `context_data` - The context data to make available during future execution
46 ///
47 /// # Returns
48 ///
49 /// Returns an [`EventContextFuture`] that will poll the wrapped future with
50 /// the provided context active.
51 fn with_event_context(self, context_data: ContextData) -> EventContextFuture<Self>
52 where
53 Self: Sized,
54 {
55 EventContextFuture {
56 future: self,
57 context_data,
58 }
59 }
60}
61
62impl<F: Future> WithEventContext for F {}
63
64/// A future wrapper that provides event context during polling.
65///
66/// This struct is created by the `with_event_context` method and should not
67/// be constructed directly. It ensures that the wrapped future has access
68/// to the specified event context data whenever it is polled.
69///
70/// The future maintains context isolation - the context is only active
71/// during the polling of the wrapped future and does not leak to other
72/// concurrent operations.
73#[pin_project]
74pub struct EventContextFuture<F> {
75 #[pin]
76 future: F,
77 context_data: ContextData,
78}
79
80impl<F: Future> Future for EventContextFuture<F> {
81 type Output = F::Output;
82
83 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
84 let this = self.project();
85 let ctx = EventContext::seed(this.context_data.clone());
86 let res = this.future.poll(cx);
87 *this.context_data = ctx.data();
88 res
89 }
90}