Skip to main content

sayiir_core/
context.rs

1use std::sync::Arc;
2
3/// Workflow execution context that provides access to metadata and codec.
4///
5/// This context is always available as a plain struct. When the `tokio` feature
6/// is enabled, it can also be stored in task-local storage and retrieved via
7/// the [`sayiir_ctx!`] macro.
8pub struct WorkflowContext<C, M> {
9    /// The unique workflow identifier.
10    pub workflow_id: Arc<str>,
11    /// The codec used for serialization/deserialization.
12    pub codec: Arc<C>,
13    /// Immutable metadata attached to the workflow.
14    pub metadata: Arc<M>,
15}
16
17impl<C, M> Clone for WorkflowContext<C, M> {
18    fn clone(&self) -> Self {
19        Self {
20            workflow_id: Arc::clone(&self.workflow_id),
21            codec: Arc::clone(&self.codec),
22            metadata: Arc::clone(&self.metadata),
23        }
24    }
25}
26
27impl<C, M> WorkflowContext<C, M> {
28    /// Create a new workflow context.
29    pub fn new(workflow_id: impl Into<Arc<str>>, codec: Arc<C>, metadata: Arc<M>) -> Self {
30        Self {
31            workflow_id: workflow_id.into(),
32            codec,
33            metadata,
34        }
35    }
36
37    #[must_use]
38    pub fn workflow_id(&self) -> &str {
39        &self.workflow_id
40    }
41
42    #[must_use]
43    pub fn codec(&self) -> Arc<C> {
44        self.codec.clone()
45    }
46
47    #[must_use]
48    pub fn metadata(&self) -> Arc<M> {
49        self.metadata.clone()
50    }
51}
52
53// ── Task-local context storage (requires tokio) ─────────────────────────
54
55#[cfg(feature = "tokio")]
56mod task_local_ctx {
57    use super::{Arc, WorkflowContext};
58    use crate::codec::Codec;
59
60    /// Type-erased workflow context for task-local storage.
61    struct ErasedContext {
62        inner: Arc<dyn std::any::Any + Send + Sync>,
63    }
64
65    impl ErasedContext {
66        fn new<C, M>(ctx: WorkflowContext<C, M>) -> Self
67        where
68            C: Codec + 'static,
69            M: Send + Sync + 'static,
70        {
71            Self {
72                inner: Arc::new(ctx) as Arc<dyn std::any::Any + Send + Sync>,
73            }
74        }
75
76        fn downcast<C, M>(&self) -> Option<WorkflowContext<C, M>>
77        where
78            C: Codec + 'static,
79            M: Send + Sync + 'static,
80        {
81            self.inner
82                .clone()
83                .downcast::<WorkflowContext<C, M>>()
84                .ok()
85                .map(|arc| {
86                    WorkflowContext::new(
87                        Arc::clone(&arc.workflow_id),
88                        Arc::clone(&arc.codec),
89                        Arc::clone(&arc.metadata),
90                    )
91                })
92        }
93    }
94
95    tokio::task_local! {
96        /// Task-local storage for workflow context.
97        static WORKFLOW_CTX: Option<ErasedContext>;
98    }
99
100    /// Set the workflow context in task-local storage and execute the future.
101    ///
102    /// This should be called by the runner before executing tasks.
103    pub async fn with_context<C, M, F, Fut>(ctx: WorkflowContext<C, M>, f: F) -> Fut::Output
104    where
105        C: Codec + 'static,
106        M: Send + Sync + 'static,
107        F: FnOnce() -> Fut,
108        Fut: std::future::Future,
109    {
110        WORKFLOW_CTX.scope(Some(ErasedContext::new(ctx)), f()).await
111    }
112
113    /// Get the workflow context from task-local storage.
114    ///
115    /// This is used internally by the `sayiir_ctx!` macro.
116    #[must_use]
117    pub fn get_context<C, M>() -> Option<WorkflowContext<C, M>>
118    where
119        C: Codec + 'static,
120        M: Send + Sync + 'static,
121    {
122        WORKFLOW_CTX
123            .try_with(|ctx_opt| ctx_opt.as_ref()?.downcast())
124            .ok()
125            .flatten()
126    }
127}
128
129#[cfg(feature = "tokio")]
130pub use task_local_ctx::{get_context, with_context};
131
132/// Macro to access the workflow context from within a task.
133///
134/// Requires the `tokio` feature. Returns `Option<WorkflowContext<C, M>>` —
135/// `None` if called outside of workflow execution context.
136///
137/// Usage:
138/// ```rust,ignore
139/// if let Some(ctx) = sayiir_ctx!() {
140///     let metadata = ctx.metadata();
141///     let codec = ctx.codec();
142/// }
143/// ```
144#[cfg(feature = "tokio")]
145#[macro_export]
146macro_rules! sayiir_ctx {
147    () => {
148        $crate::context::get_context()
149    };
150}