Skip to main content

temporalio_sdk/
interceptors.rs

1//! User-definable interceptors are defined in this module
2
3use crate::{
4    Worker, WorkerRunError,
5    activities::{ActivityContext, ActivityError, ActivityInfo},
6};
7use futures_util::future::{BoxFuture, LocalBoxFuture};
8#[cfg(feature = "experimental")]
9use std::sync::OnceLock;
10use std::{any::Any, collections::HashMap, sync::Arc};
11use temporalio_common::{
12    data_converters::{
13        GenericPayloadConverter, PayloadConversionError, SerializationContext, TemporalSerializable,
14    },
15    protos::{
16        coresdk::{
17            workflow_activation::WorkflowActivation,
18            workflow_completion::WorkflowActivationCompletion,
19        },
20        temporal::api::common::v1::Payload,
21    },
22};
23
24mod activity_execution_value {
25    use super::*;
26
27    pub trait Sealed {
28        fn to_activity_payload(
29            &self,
30            context: &SerializationContext<'_>,
31        ) -> Result<Payload, PayloadConversionError>;
32    }
33
34    impl<T> Sealed for T
35    where
36        T: Any + TemporalSerializable + Send + Sync,
37    {
38        fn to_activity_payload(
39            &self,
40            context: &SerializationContext<'_>,
41        ) -> Result<Payload, PayloadConversionError> {
42            context.converter.to_payload(context, self)
43        }
44    }
45}
46
47/// Continuation for an interceptor operation.
48///
49/// Interceptor implementations call [`Next::run`] to invoke the next step of the chain.
50pub struct Next<'a, I, O> {
51    inner: Box<dyn FnOnce(I) -> O + Send + 'a>,
52}
53
54impl<'a, I, O> Next<'a, I, O> {
55    pub(crate) fn new(f: impl FnOnce(I) -> O + Send + 'a) -> Self {
56        Self { inner: Box::new(f) }
57    }
58
59    /// Continue the call chain with the provided input.
60    pub fn run(self, input: I) -> O {
61        (self.inner)(input)
62    }
63}
64
65#[cfg_attr(not(feature = "experimental"), allow(unreachable_pub))]
66mod worker_lifecycle {
67    use super::*;
68
69    /// Implementors can intercept certain actions that happen within the Worker.
70    ///
71    /// Advanced usage only.
72    /// **Experimental:** This API may change or be removed.
73    #[async_trait::async_trait(?Send)]
74    pub trait WorkerInterceptor: Send + Sync {
75        /// Intercept the running of a worker.
76        fn run_worker<'a>(
77            &'a self,
78            input: RunWorkerInput<'a>,
79            next: Next<'a, RunWorkerInput<'a>, LocalBoxFuture<'a, Result<(), WorkerRunError>>>,
80        ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
81            next.run(input)
82        }
83
84        /// Intercept the running of a worker created for workflow replay.
85        fn with_workflow_replay_worker<'a>(
86            &'a self,
87            input: WithWorkflowReplayWorkerInput<'a>,
88            next: Next<
89                'a,
90                WithWorkflowReplayWorkerInput<'a>,
91                LocalBoxFuture<'a, Result<(), WorkerRunError>>,
92            >,
93        ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
94            next.run(input)
95        }
96
97        /// Called every time a workflow activation completes (just before sending the completion to
98        /// core).
99        async fn on_workflow_activation_completion(
100            &self,
101            _completion: &WorkflowActivationCompletion,
102        ) {
103        }
104        /// Called after the worker has initiated shutdown and the workflow/activity polling loops
105        /// have exited, but just before waiting for the inner core worker shutdown
106        fn on_shutdown(&self, _sdk_worker: &Worker) {}
107        /// Called every time a workflow is about to be activated
108        async fn on_workflow_activation(
109            &self,
110            _activation: &WorkflowActivation,
111        ) -> Result<(), anyhow::Error> {
112            Ok(())
113        }
114    }
115
116    /// Input to [`WorkerInterceptor::run_worker`].
117    #[derive(Debug)]
118    #[non_exhaustive]
119    pub struct RunWorkerInput<'a> {
120        /// The worker being run.
121        pub worker: &'a mut Worker,
122    }
123
124    impl<'a> RunWorkerInput<'a> {
125        pub(crate) fn new(worker: &'a mut Worker) -> Self {
126            Self { worker }
127        }
128    }
129
130    /// Input to [`WorkerInterceptor::with_workflow_replay_worker`].
131    #[derive(Debug)]
132    #[non_exhaustive]
133    pub struct WithWorkflowReplayWorkerInput<'a> {
134        /// The worker created for this replay operation.
135        pub worker: &'a mut Worker,
136    }
137
138    impl<'a> WithWorkflowReplayWorkerInput<'a> {
139        pub(crate) fn new(worker: &'a mut Worker) -> Self {
140            Self { worker }
141        }
142    }
143
144    pub(crate) fn call_run_worker<'a>(
145        interceptors: &'a [Arc<dyn WorkerInterceptor>],
146        input: RunWorkerInput<'a>,
147        terminal: Next<'a, RunWorkerInput<'a>, LocalBoxFuture<'a, Result<(), WorkerRunError>>>,
148    ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
149        if let Some((interceptor, remaining)) = interceptors.split_first() {
150            let next = Next::new(move |input| call_run_worker(remaining, input, terminal));
151            interceptor.run_worker(input, next)
152        } else {
153            terminal.run(input)
154        }
155    }
156
157    pub(crate) fn call_with_workflow_replay_worker<'a>(
158        interceptors: &'a [Arc<dyn WorkerInterceptor>],
159        input: WithWorkflowReplayWorkerInput<'a>,
160        terminal: Next<
161            'a,
162            WithWorkflowReplayWorkerInput<'a>,
163            LocalBoxFuture<'a, Result<(), WorkerRunError>>,
164        >,
165    ) -> LocalBoxFuture<'a, Result<(), WorkerRunError>> {
166        if let Some((interceptor, remaining)) = interceptors.split_first() {
167            let next = Next::new(move |input| {
168                call_with_workflow_replay_worker(remaining, input, terminal)
169            });
170            interceptor.with_workflow_replay_worker(input, next)
171        } else {
172            terminal.run(input)
173        }
174    }
175}
176
177#[cfg(not(feature = "experimental"))]
178pub(crate) use worker_lifecycle::{
179    RunWorkerInput, WithWorkflowReplayWorkerInput, WorkerInterceptor,
180};
181#[cfg(feature = "experimental")]
182pub use worker_lifecycle::{RunWorkerInput, WithWorkflowReplayWorkerInput, WorkerInterceptor};
183pub(crate) use worker_lifecycle::{call_run_worker, call_with_workflow_replay_worker};
184
185/// Activity execution data passed to [`ActivityInboundInterceptor::execute_activity`].
186#[non_exhaustive]
187pub struct ExecuteActivityInput {
188    context: ActivityContext,
189    args: Box<dyn Any + Send + Sync>,
190}
191
192impl ExecuteActivityInput {
193    pub(crate) fn new(context: ActivityContext, args: Box<dyn Any + Send + Sync>) -> Self {
194        Self { context, args }
195    }
196
197    pub(crate) fn into_parts(self) -> (ActivityContext, Box<dyn Any + Send + Sync>) {
198        (self.context, self.args)
199    }
200
201    /// Information about the activity execution.
202    pub fn activity_info(&self) -> &ActivityInfo {
203        self.context.info()
204    }
205
206    /// Headers attached to this activity.
207    pub fn headers(&self) -> &HashMap<String, Payload> {
208        self.context.headers()
209    }
210
211    /// Mutably access headers attached to this activity.
212    pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
213        self.context.headers_mut()
214    }
215
216    /// Attempt to access the decoded activity arguments as a concrete type.
217    pub fn args_ref<T: Any>(&self) -> Option<&T> {
218        self.args.downcast_ref()
219    }
220
221    /// Attempt to mutably access the decoded activity arguments as a concrete type.
222    pub fn args_mut<T: Any>(&mut self) -> Option<&mut T> {
223        self.args.downcast_mut()
224    }
225}
226
227/// Type-erased activity output carried through the activity interceptor chain.
228pub trait ActivityExecutionValue:
229    Any + TemporalSerializable + Send + Sync + activity_execution_value::Sealed
230{
231    /// Access this value as [`Any`] for type-specific inspection.
232    fn as_any(&self) -> &dyn Any;
233}
234
235impl<T> ActivityExecutionValue for T
236where
237    T: Any + TemporalSerializable + Send + Sync,
238{
239    fn as_any(&self) -> &dyn Any {
240        self
241    }
242}
243
244impl dyn ActivityExecutionValue {
245    /// Attempt to access the activity output as a concrete type.
246    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
247        self.as_any().downcast_ref()
248    }
249
250    pub(crate) fn serialize_payload(
251        &self,
252        context: &SerializationContext<'_>,
253    ) -> Result<Payload, PayloadConversionError> {
254        self.to_activity_payload(context)
255    }
256}
257
258/// Result of an activity execution carried through the interceptor chain.
259pub type ExecuteActivityResult = Result<Box<dyn ActivityExecutionValue>, ActivityError>;
260
261/// Future produced by activity inbound interceptors.
262pub type ExecuteActivityOutput<'a> = BoxFuture<'a, ExecuteActivityResult>;
263
264/// Inbound interceptor for activity calls coming from the server.
265///
266/// Must be implemented by inbound activity interceptors.
267pub trait ActivityInboundInterceptor: Send + Sync + 'static {
268    /// Called to invoke the activity.
269    fn execute_activity<'a>(
270        &'a self,
271        input: ExecuteActivityInput,
272        next: Next<'a, ExecuteActivityInput, ExecuteActivityOutput<'a>>,
273    ) -> ExecuteActivityOutput<'a> {
274        next.run(input)
275    }
276}
277
278/// An interceptor that allows you to fetch the exit value of the workflow if and when it is set
279#[cfg(feature = "experimental")]
280#[derive(Default)]
281pub struct ReturnWorkflowExitValueInterceptor {
282    result_value: Arc<OnceLock<Payload>>,
283}
284
285#[cfg(feature = "experimental")]
286impl ReturnWorkflowExitValueInterceptor {
287    /// Can be used to fetch the workflow result if/when it is determined
288    pub fn result_handle(&self) -> Arc<OnceLock<Payload>> {
289        self.result_value.clone()
290    }
291}
292
293#[async_trait::async_trait(?Send)]
294#[cfg(feature = "experimental")]
295impl WorkerInterceptor for ReturnWorkflowExitValueInterceptor {
296    async fn on_workflow_activation_completion(&self, c: &WorkflowActivationCompletion) {
297        if let Some(v) = c.complete_workflow_execution_value() {
298            let _ = self.result_value.set(v.clone());
299        }
300    }
301}