use crate::{
ActivityOptions, BaseWorkflowContext, CancellableFuture, CancellableFutureWithReason,
ChildWorkflowOptions, ContinueAsNewOptions, ExternalWorkflowHandle, LocalActivityOptions,
NexusOperationOptions, StartChildWorkflowOutput, StartedChildWorkflow, StartedNexusOperation,
TimerOptions, WorkflowContextView,
runtime::{
entry::WorkflowError,
model::{
CancelExternalWfResult, NexusStartResult, TimerResult, WorkflowResult,
WorkflowTermination,
},
},
};
use futures_util::{
FutureExt,
future::{Fuse, FusedFuture, LocalBoxFuture},
};
use std::{
any::Any,
collections::HashMap,
convert::Infallible,
future::Future,
pin::Pin,
rc::Rc,
sync::Arc,
task::{Context, Poll},
time::SystemTime,
};
use temporalio_common_wasm::{
ActivityDefinition, WorkflowDefinition,
data_converters::{
GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
SerializationContextData, TemporalDeserializable, TemporalSerializable,
},
error::{
ActivityExecutionError, ChildWorkflowExecutionError, ChildWorkflowStartError,
WorkflowSignalError,
},
protos::temporal::api::{common::v1::Payload, failure::v1::Failure},
search_attributes::SearchAttributes,
};
mod workflow_output_value {
use super::*;
pub trait Sealed {
fn to_workflow_payload(
&self,
context: &SerializationContext<'_>,
) -> Result<Payload, PayloadConversionError>;
}
impl<T> Sealed for T
where
T: Any + TemporalSerializable,
{
fn to_workflow_payload(
&self,
context: &SerializationContext<'_>,
) -> Result<Payload, PayloadConversionError> {
context.converter.to_payload(context, self)
}
}
}
pub trait WorkflowOutputValue: Any + TemporalSerializable + workflow_output_value::Sealed {
fn as_any(&self) -> &dyn Any;
}
impl<T> WorkflowOutputValue for T
where
T: Any + TemporalSerializable,
{
fn as_any(&self) -> &dyn Any {
self
}
}
impl dyn WorkflowOutputValue {
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.as_any().downcast_ref()
}
pub(crate) fn serialize_payload(
&self,
context: &SerializationContext<'_>,
) -> Result<Payload, PayloadConversionError> {
self.to_workflow_payload(context)
}
}
pub(crate) fn serialize_workflow_output(
output: &dyn WorkflowOutputValue,
converter: &PayloadConverter,
) -> Result<Payload, PayloadConversionError> {
let ctx = SerializationContext {
data: &SerializationContextData::Workflow,
converter,
};
output.serialize_payload(&ctx)
}
pub type ExecuteWorkflowResult = WorkflowResult<Box<dyn WorkflowOutputValue>>;
pub type HandleSignalResult = Result<(), WorkflowError>;
pub type HandleUpdateResult = Result<Box<dyn WorkflowOutputValue>, WorkflowError>;
pub type HandleQueryResult = Result<Box<dyn WorkflowOutputValue>, WorkflowError>;
pub type ValidateUpdateResult = Result<(), WorkflowError>;
pub struct WorkflowInterceptorFuture<'a, T>(LocalBoxFuture<'a, T>);
impl<'a, T> WorkflowInterceptorFuture<'a, T> {
pub fn new(fut: impl Future<Output = T> + 'a) -> Self {
Self(fut.boxed_local())
}
}
impl<'a, T> Unpin for WorkflowInterceptorFuture<'a, T> {}
impl<T> Future for WorkflowInterceptorFuture<'_, T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(cx)
}
}
pub struct WorkflowNext<'a, I, O> {
inner: Box<dyn FnOnce(I) -> O + 'a>,
}
impl<'a, I, O> WorkflowNext<'a, I, O> {
pub(crate) fn new(f: impl FnOnce(I) -> O + 'a) -> Self {
Self { inner: Box::new(f) }
}
pub fn run(self, input: I) -> O {
(self.inner)(input)
}
}
#[derive(Clone)]
pub struct WorkflowInterceptorContext {
base: BaseWorkflowContext,
}
impl WorkflowInterceptorContext {
pub(crate) fn new(base: BaseWorkflowContext) -> Self {
Self { base }
}
pub fn workflow_id(&self) -> &str {
self.base.workflow_id()
}
pub fn run_id(&self) -> &str {
self.base.run_id()
}
pub fn namespace(&self) -> &str {
self.base.namespace()
}
pub fn task_queue(&self) -> &str {
self.base.task_queue()
}
pub fn workflow_type(&self) -> &str {
self.base.workflow_type()
}
pub fn workflow_time(&self) -> Option<SystemTime> {
self.base.workflow_time()
}
pub fn history_length(&self) -> u32 {
self.base.history_length()
}
pub fn search_attributes(&self) -> SearchAttributes {
self.base.search_attributes()
}
pub fn is_replaying(&self) -> bool {
self.base.is_replaying()
}
pub fn is_replaying_history_events(&self) -> bool {
self.base.is_replaying_history_events()
}
pub fn payload_converter(&self) -> &PayloadConverter {
self.base.payload_converter()
}
pub fn timer<T: Into<TimerOptions>>(
&self,
opts: T,
) -> impl CancellableFuture<TimerResult> + use<T> {
self.base.timer(opts)
}
pub fn execute_activity<AD: ActivityDefinition>(
&self,
activity: AD,
input: impl Into<AD::Input>,
opts: ActivityOptions,
) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
where
AD::Output: TemporalDeserializable,
{
self.base.execute_activity(activity, input, opts)
}
pub fn execute_local_activity<AD: ActivityDefinition>(
&self,
activity: AD,
input: impl Into<AD::Input>,
opts: LocalActivityOptions,
) -> impl CancellableFuture<Result<AD::Output, ActivityExecutionError>>
where
AD::Output: TemporalDeserializable,
{
self.base.execute_local_activity(activity, input, opts)
}
pub fn start_child_workflow<WD: WorkflowDefinition + 'static>(
&self,
workflow: WD,
input: impl Into<WD::Input>,
opts: ChildWorkflowOptions,
) -> impl CancellableFutureWithReason<Result<StartedChildWorkflow<WD>, ChildWorkflowStartError>>
where
WD::Output: TemporalDeserializable,
{
self.base.start_child_workflow(workflow, input, opts)
}
pub fn external_workflow(
&self,
workflow_id: impl Into<String>,
run_id: Option<String>,
) -> ExternalWorkflowHandle {
self.base.external_workflow(workflow_id, run_id)
}
pub fn start_nexus_operation(
&self,
opts: NexusOperationOptions,
) -> impl CancellableFuture<NexusStartResult> {
self.base.start_nexus_operation(opts)
}
}
#[derive(Clone)]
pub struct SyncWorkflowInterceptorContext {
base: BaseWorkflowContext,
}
impl SyncWorkflowInterceptorContext {
pub(crate) fn new(base: BaseWorkflowContext) -> Self {
Self { base }
}
pub fn workflow_id(&self) -> &str {
self.base.workflow_id()
}
pub fn run_id(&self) -> &str {
self.base.run_id()
}
pub fn namespace(&self) -> &str {
self.base.namespace()
}
pub fn task_queue(&self) -> &str {
self.base.task_queue()
}
pub fn workflow_type(&self) -> &str {
self.base.workflow_type()
}
pub fn workflow_time(&self) -> Option<SystemTime> {
self.base.workflow_time()
}
pub fn history_length(&self) -> u32 {
self.base.history_length()
}
pub fn search_attributes(&self) -> SearchAttributes {
self.base.search_attributes()
}
pub fn is_replaying(&self) -> bool {
self.base.is_replaying()
}
pub fn is_replaying_history_events(&self) -> bool {
self.base.is_replaying_history_events()
}
pub fn payload_converter(&self) -> &PayloadConverter {
self.base.payload_converter()
}
}
struct DecodedInput {
value: Option<Box<dyn Any>>,
headers: HashMap<String, Payload>,
}
impl DecodedInput {
fn new(value: Option<Box<dyn Any>>, headers: HashMap<String, Payload>) -> Self {
Self { value, headers }
}
fn input_ref<T: Any>(&self) -> Option<&T> {
self.value.as_ref()?.downcast_ref()
}
fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.value.as_mut()?.downcast_mut()
}
fn headers(&self) -> &HashMap<String, Payload> {
&self.headers
}
fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
&mut self.headers
}
fn into_parts(self) -> (Option<Box<dyn Any>>, HashMap<String, Payload>) {
(self.value, self.headers)
}
}
#[non_exhaustive]
pub struct InitializeWorkflowInput {
decoded: DecodedInput,
}
impl InitializeWorkflowInput {
pub(crate) fn new(value: Option<Box<dyn Any>>, headers: HashMap<String, Payload>) -> Self {
Self {
decoded: DecodedInput::new(value, headers),
}
}
pub(crate) fn into_parts(self) -> (Option<Box<dyn Any>>, HashMap<String, Payload>) {
self.decoded.into_parts()
}
pub fn input_ref<T: Any>(&self) -> Option<&T> {
self.decoded.input_ref()
}
pub fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.decoded.input_mut()
}
pub fn headers(&self) -> &HashMap<String, Payload> {
self.decoded.headers()
}
pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
self.decoded.headers_mut()
}
}
pub struct InitializeWorkflowOutput {
_private: (),
}
impl InitializeWorkflowOutput {
pub(crate) fn new() -> Self {
Self { _private: () }
}
}
#[non_exhaustive]
pub struct ExecuteWorkflowInput {
decoded: DecodedInput,
}
impl ExecuteWorkflowInput {
pub(crate) fn new(value: Option<Box<dyn Any>>, headers: HashMap<String, Payload>) -> Self {
Self {
decoded: DecodedInput::new(value, headers),
}
}
pub(crate) fn into_parts(self) -> (Option<Box<dyn Any>>, HashMap<String, Payload>) {
self.decoded.into_parts()
}
pub fn input_ref<T: Any>(&self) -> Option<&T> {
self.decoded.input_ref()
}
pub fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.decoded.input_mut()
}
pub fn headers(&self) -> &HashMap<String, Payload> {
self.decoded.headers()
}
pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
self.decoded.headers_mut()
}
}
macro_rules! handler_input {
($name:ident, $doc:literal, $field:ident, $field_doc:literal $(, $id_field:ident, $id_doc:literal)?) => {
#[doc = $doc]
#[non_exhaustive]
pub struct $name {
$($id_field: String,)?
$field: String,
decoded: DecodedInput,
}
impl $name {
pub(crate) fn new(
$($id_field: String,)?
$field: String,
value: Box<dyn Any>,
headers: HashMap<String, Payload>,
) -> Self {
Self {
$($id_field,)?
$field,
decoded: DecodedInput::new(Some(value), headers),
}
}
pub(crate) fn into_parts(self) -> (String, Box<dyn Any>, HashMap<String, Payload>) {
let (value, headers) = self.decoded.into_parts();
(
self.$field,
value.expect("handler input must exist after typed decode"),
headers,
)
}
#[doc = $field_doc]
pub fn name(&self) -> &str {
&self.$field
}
$(
#[doc = $id_doc]
pub fn id(&self) -> &str {
&self.$id_field
}
)?
pub fn input_ref<T: Any>(&self) -> Option<&T> {
self.decoded.input_ref()
}
pub fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.decoded.input_mut()
}
pub fn headers(&self) -> &HashMap<String, Payload> {
self.decoded.headers()
}
pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
self.decoded.headers_mut()
}
}
};
}
handler_input!(
HandleSignalInput,
"Input passed to [`WorkflowInterceptor::handle_signal`].",
signal_name,
"Return the signal name."
);
handler_input!(
HandleUpdateInput,
"Input passed to [`WorkflowInterceptor::handle_update`].",
update_name,
"Return the update name.",
update_id,
"Return the update ID."
);
handler_input!(
HandleQueryInput,
"Input passed to [`WorkflowInterceptor::handle_query`].",
query_name,
"Return the query name.",
query_id,
"Return the query ID."
);
#[non_exhaustive]
pub struct ValidateUpdateInput {
update_id: String,
update_name: String,
decoded: DecodedInput,
}
impl ValidateUpdateInput {
pub(crate) fn new(
update_id: String,
update_name: String,
value: Box<dyn Any>,
headers: HashMap<String, Payload>,
) -> Self {
Self {
update_id,
update_name,
decoded: DecodedInput::new(Some(value), headers),
}
}
pub(crate) fn into_parts(self) -> (String, Box<dyn Any>, HashMap<String, Payload>) {
let (value, headers) = self.decoded.into_parts();
(
self.update_name,
value.expect("update validation input must exist after typed decode"),
headers,
)
}
pub fn name(&self) -> &str {
&self.update_name
}
pub fn id(&self) -> &str {
&self.update_id
}
pub fn input_ref<T: Any>(&self) -> Option<&T> {
self.decoded.input_ref()
}
pub fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.decoded.input_mut()
}
pub fn headers(&self) -> &HashMap<String, Payload> {
self.decoded.headers()
}
pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
self.decoded.headers_mut()
}
}
pub trait WorkflowOutboundValue: Any {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Any> WorkflowOutboundValue for T {
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
impl dyn WorkflowOutboundValue {
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.as_any().downcast_ref()
}
pub fn downcast<T: Any>(self: Box<Self>) -> Result<Box<T>, Box<dyn Any>> {
self.into_any().downcast()
}
}
pub struct WorkflowOutboundFuture<T> {
state: WorkflowOutboundFutureState<T>,
}
enum WorkflowOutboundFutureState<T> {
Running(Fuse<LocalBoxFuture<'static, T>>),
Prefetched(Option<T>),
Terminated,
}
impl<T> WorkflowOutboundFuture<T> {
pub fn new(future: impl Future<Output = T> + 'static) -> Self {
Self {
state: WorkflowOutboundFutureState::Running(future.boxed_local().fuse()),
}
}
pub fn ready(value: T) -> Self
where
T: 'static,
{
Self::new(async move { value })
}
pub fn map<U>(self, map: impl FnOnce(T) -> U + 'static) -> WorkflowOutboundFuture<U>
where
T: 'static,
U: 'static,
{
WorkflowOutboundFuture::new(async move { map(self.await) })
}
pub(crate) fn poll_for_construction(&mut self, cx: &mut Context<'_>) {
let WorkflowOutboundFutureState::Running(future) = &mut self.state else {
return;
};
if let Poll::Ready(value) = future.poll_unpin(cx) {
self.state = WorkflowOutboundFutureState::Prefetched(Some(value));
}
}
}
impl<T> Unpin for WorkflowOutboundFuture<T> {}
impl<T> Future for WorkflowOutboundFuture<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match &mut self.state {
WorkflowOutboundFutureState::Running(future) => {
let result = future.poll_unpin(cx);
if result.is_ready() {
self.state = WorkflowOutboundFutureState::Terminated;
}
result
}
WorkflowOutboundFutureState::Prefetched(value) => {
let value = value
.take()
.expect("outbound future polled after completion");
self.state = WorkflowOutboundFutureState::Terminated;
Poll::Ready(value)
}
WorkflowOutboundFutureState::Terminated => {
panic!("outbound future polled after completion")
}
}
}
}
impl<T> FusedFuture for WorkflowOutboundFuture<T> {
fn is_terminated(&self) -> bool {
matches!(self.state, WorkflowOutboundFutureState::Terminated)
}
}
#[derive(Clone)]
pub struct WorkflowCancellationHandle {
cancel: Rc<dyn Fn(Option<String>)>,
}
impl WorkflowCancellationHandle {
pub fn new(cancel: impl Fn(Option<String>) + 'static) -> Self {
Self {
cancel: Rc::new(cancel),
}
}
pub(crate) fn noop() -> Self {
Self::new(|_| {})
}
pub fn cancel(&self, reason: Option<String>) {
(self.cancel)(reason);
}
}
pub struct CancellableWorkflowOutboundFuture<T> {
inner: WorkflowOutboundFuture<T>,
cancellation: WorkflowCancellationHandle,
}
impl<T> CancellableWorkflowOutboundFuture<T> {
pub fn new(
future: impl Future<Output = T> + 'static,
cancellation: WorkflowCancellationHandle,
) -> Self {
Self {
inner: WorkflowOutboundFuture::new(future),
cancellation,
}
}
pub fn cancellation_handle(&self) -> WorkflowCancellationHandle {
self.cancellation.clone()
}
pub fn map<U>(self, map: impl FnOnce(T) -> U + 'static) -> CancellableWorkflowOutboundFuture<U>
where
T: 'static,
U: 'static,
{
let cancellation = self.cancellation.clone();
CancellableWorkflowOutboundFuture::new(async move { map(self.await) }, cancellation)
}
pub(crate) fn poll_for_construction(&mut self, cx: &mut Context<'_>) {
self.inner.poll_for_construction(cx);
}
}
impl<T> Unpin for CancellableWorkflowOutboundFuture<T> {}
impl<T> Future for CancellableWorkflowOutboundFuture<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx)
}
}
impl<T> FusedFuture for CancellableWorkflowOutboundFuture<T> {
fn is_terminated(&self) -> bool {
self.inner.is_terminated()
}
}
impl<T> CancellableFuture<T> for CancellableWorkflowOutboundFuture<T> {
fn cancel(&self) {
if !self.inner.is_terminated() {
self.cancellation.cancel(None);
}
}
}
impl<T> CancellableFutureWithReason<T> for CancellableWorkflowOutboundFuture<T> {
fn cancel_with_reason(&self, reason: String) {
if !self.inner.is_terminated() {
self.cancellation.cancel(Some(reason));
}
}
}
macro_rules! typed_outbound_input {
($name:ident) => {
impl $name {
pub fn input_ref<T: Any>(&self) -> Option<&T> {
self.decoded.input_ref()
}
pub fn input_mut<T: Any>(&mut self) -> Option<&mut T> {
self.decoded.input_mut()
}
pub fn headers(&self) -> &HashMap<String, Payload> {
self.decoded.headers()
}
pub fn headers_mut(&mut self) -> &mut HashMap<String, Payload> {
self.decoded.headers_mut()
}
}
};
}
#[non_exhaustive]
pub struct StartTimerInput {
options: TimerOptions,
}
impl StartTimerInput {
pub(crate) fn new(options: TimerOptions) -> Self {
Self { options }
}
pub(crate) fn into_options(self) -> TimerOptions {
self.options
}
pub fn options(&self) -> &TimerOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut TimerOptions {
&mut self.options
}
}
#[non_exhaustive]
pub struct ScheduleActivityInput {
activity_type: String,
decoded: DecodedInput,
options: ActivityOptions,
}
impl ScheduleActivityInput {
pub(crate) fn new(
activity_type: String,
input: Box<dyn Any>,
options: ActivityOptions,
) -> Self {
Self {
activity_type,
decoded: DecodedInput::new(Some(input), HashMap::new()),
options,
}
}
pub(crate) fn into_parts(
self,
) -> (
String,
Box<dyn Any>,
HashMap<String, Payload>,
ActivityOptions,
) {
let (input, headers) = self.decoded.into_parts();
(
self.activity_type,
input.expect("activity input must exist"),
headers,
self.options,
)
}
pub fn activity_type(&self) -> &str {
&self.activity_type
}
pub fn activity_type_mut(&mut self) -> &mut String {
&mut self.activity_type
}
pub fn options(&self) -> &ActivityOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut ActivityOptions {
&mut self.options
}
}
typed_outbound_input!(ScheduleActivityInput);
#[non_exhaustive]
pub struct ScheduleLocalActivityInput {
activity_type: String,
decoded: DecodedInput,
options: LocalActivityOptions,
}
impl ScheduleLocalActivityInput {
pub(crate) fn new(
activity_type: String,
input: Box<dyn Any>,
options: LocalActivityOptions,
) -> Self {
Self {
activity_type,
decoded: DecodedInput::new(Some(input), HashMap::new()),
options,
}
}
pub(crate) fn into_parts(
self,
) -> (
String,
Box<dyn Any>,
HashMap<String, Payload>,
LocalActivityOptions,
) {
let (input, headers) = self.decoded.into_parts();
(
self.activity_type,
input.expect("local activity input must exist"),
headers,
self.options,
)
}
pub fn activity_type(&self) -> &str {
&self.activity_type
}
pub fn activity_type_mut(&mut self) -> &mut String {
&mut self.activity_type
}
pub fn options(&self) -> &LocalActivityOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut LocalActivityOptions {
&mut self.options
}
}
typed_outbound_input!(ScheduleLocalActivityInput);
#[non_exhaustive]
pub struct StartChildWorkflowInput {
workflow_type: String,
decoded: DecodedInput,
options: ChildWorkflowOptions,
}
impl StartChildWorkflowInput {
pub(crate) fn new(
workflow_type: String,
input: Box<dyn Any>,
options: ChildWorkflowOptions,
) -> Self {
Self {
workflow_type,
decoded: DecodedInput::new(Some(input), HashMap::new()),
options,
}
}
pub(crate) fn into_parts(
self,
) -> (
String,
Box<dyn Any>,
HashMap<String, Payload>,
ChildWorkflowOptions,
) {
let (input, headers) = self.decoded.into_parts();
(
self.workflow_type,
input.expect("child workflow input must exist"),
headers,
self.options,
)
}
pub fn workflow_type(&self) -> &str {
&self.workflow_type
}
pub fn workflow_type_mut(&mut self) -> &mut String {
&mut self.workflow_type
}
pub fn options(&self) -> &ChildWorkflowOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut ChildWorkflowOptions {
&mut self.options
}
}
typed_outbound_input!(StartChildWorkflowInput);
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SignalWorkflowTarget {
Child {
workflow_id: String,
},
External {
namespace: String,
workflow_id: String,
run_id: Option<String>,
},
}
#[non_exhaustive]
pub struct SignalWorkflowInput {
signal_name: String,
target: SignalWorkflowTarget,
decoded: DecodedInput,
}
impl SignalWorkflowInput {
pub(crate) fn new(
signal_name: String,
target: SignalWorkflowTarget,
input: Box<dyn Any>,
) -> Self {
Self {
signal_name,
target,
decoded: DecodedInput::new(Some(input), HashMap::new()),
}
}
pub(crate) fn into_parts(
self,
) -> (
String,
SignalWorkflowTarget,
Box<dyn Any>,
HashMap<String, Payload>,
) {
let (input, headers) = self.decoded.into_parts();
(
self.signal_name,
self.target,
input.expect("signal input must exist"),
headers,
)
}
pub fn signal_name(&self) -> &str {
&self.signal_name
}
pub fn signal_name_mut(&mut self) -> &mut String {
&mut self.signal_name
}
pub fn target(&self) -> &SignalWorkflowTarget {
&self.target
}
pub fn target_mut(&mut self) -> &mut SignalWorkflowTarget {
&mut self.target
}
}
typed_outbound_input!(SignalWorkflowInput);
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CancelExternalWorkflowInput {
pub workflow_id: String,
pub run_id: Option<String>,
pub reason: Option<String>,
}
#[non_exhaustive]
pub struct ContinueAsNewInput {
decoded: DecodedInput,
options: ContinueAsNewOptions,
}
impl ContinueAsNewInput {
pub(crate) fn new(input: Box<dyn Any>, options: ContinueAsNewOptions) -> Self {
Self {
decoded: DecodedInput::new(Some(input), HashMap::new()),
options,
}
}
pub(crate) fn into_parts(
self,
) -> (Box<dyn Any>, HashMap<String, Payload>, ContinueAsNewOptions) {
let (input, headers) = self.decoded.into_parts();
(
input.expect("continue-as-new input must exist"),
headers,
self.options,
)
}
pub fn options(&self) -> &ContinueAsNewOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut ContinueAsNewOptions {
&mut self.options
}
}
typed_outbound_input!(ContinueAsNewInput);
#[non_exhaustive]
pub struct StartNexusOperationInput {
options: NexusOperationOptions,
}
impl StartNexusOperationInput {
pub(crate) fn new(options: NexusOperationOptions) -> Self {
Self { options }
}
pub(crate) fn into_options(self) -> NexusOperationOptions {
self.options
}
pub fn options(&self) -> &NexusOperationOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut NexusOperationOptions {
&mut self.options
}
}
pub type ScheduleActivityResult = Result<Box<dyn WorkflowOutboundValue>, ActivityExecutionError>;
pub type ChildWorkflowOutboundResult =
Result<Box<dyn WorkflowOutboundValue>, ChildWorkflowExecutionError>;
pub type SignalWorkflowResult = Result<(), WorkflowSignalError>;
pub type StartChildWorkflowResult = Result<StartChildWorkflowOutput, ChildWorkflowStartError>;
pub type StartNexusOperationResult = Result<StartedNexusOperation, Failure>;
pub type ContinueAsNewResult = Result<Infallible, WorkflowTermination>;
pub trait WorkflowInterceptor: 'static {
fn initialize_workflow(
&self,
_ctx: WorkflowContextView,
input: InitializeWorkflowInput,
next: WorkflowNext<'_, InitializeWorkflowInput, InitializeWorkflowOutput>,
) -> InitializeWorkflowOutput {
next.run(input)
}
fn execute<'a>(
&'a self,
_ctx: WorkflowInterceptorContext,
input: ExecuteWorkflowInput,
next: WorkflowNext<
'a,
ExecuteWorkflowInput,
WorkflowInterceptorFuture<'a, ExecuteWorkflowResult>,
>,
) -> WorkflowInterceptorFuture<'a, ExecuteWorkflowResult> {
next.run(input)
}
fn handle_signal<'a>(
&'a self,
_ctx: WorkflowInterceptorContext,
input: HandleSignalInput,
next: WorkflowNext<
'a,
HandleSignalInput,
WorkflowInterceptorFuture<'a, HandleSignalResult>,
>,
) -> WorkflowInterceptorFuture<'a, HandleSignalResult> {
next.run(input)
}
fn handle_update<'a>(
&'a self,
_ctx: WorkflowInterceptorContext,
input: HandleUpdateInput,
next: WorkflowNext<
'a,
HandleUpdateInput,
WorkflowInterceptorFuture<'a, HandleUpdateResult>,
>,
) -> WorkflowInterceptorFuture<'a, HandleUpdateResult> {
next.run(input)
}
fn handle_query(
&self,
_ctx: SyncWorkflowInterceptorContext,
input: HandleQueryInput,
next: WorkflowNext<'_, HandleQueryInput, HandleQueryResult>,
) -> HandleQueryResult {
next.run(input)
}
fn validate_update(
&self,
_ctx: SyncWorkflowInterceptorContext,
input: ValidateUpdateInput,
next: WorkflowNext<'_, ValidateUpdateInput, ValidateUpdateResult>,
) -> ValidateUpdateResult {
next.run(input)
}
fn start_timer(
&self,
_ctx: WorkflowInterceptorContext,
input: StartTimerInput,
next: WorkflowNext<
'static,
StartTimerInput,
CancellableWorkflowOutboundFuture<TimerResult>,
>,
) -> CancellableWorkflowOutboundFuture<TimerResult> {
next.run(input)
}
fn schedule_activity(
&self,
_ctx: WorkflowInterceptorContext,
input: ScheduleActivityInput,
next: WorkflowNext<
'static,
ScheduleActivityInput,
CancellableWorkflowOutboundFuture<ScheduleActivityResult>,
>,
) -> CancellableWorkflowOutboundFuture<ScheduleActivityResult> {
next.run(input)
}
fn schedule_local_activity(
&self,
_ctx: WorkflowInterceptorContext,
input: ScheduleLocalActivityInput,
next: WorkflowNext<
'static,
ScheduleLocalActivityInput,
CancellableWorkflowOutboundFuture<ScheduleActivityResult>,
>,
) -> CancellableWorkflowOutboundFuture<ScheduleActivityResult> {
next.run(input)
}
fn start_child_workflow(
&self,
_ctx: WorkflowInterceptorContext,
input: StartChildWorkflowInput,
next: WorkflowNext<
'static,
StartChildWorkflowInput,
CancellableWorkflowOutboundFuture<StartChildWorkflowResult>,
>,
) -> CancellableWorkflowOutboundFuture<StartChildWorkflowResult> {
next.run(input)
}
fn signal_workflow(
&self,
_ctx: WorkflowInterceptorContext,
input: SignalWorkflowInput,
next: WorkflowNext<
'static,
SignalWorkflowInput,
CancellableWorkflowOutboundFuture<SignalWorkflowResult>,
>,
) -> CancellableWorkflowOutboundFuture<SignalWorkflowResult> {
next.run(input)
}
fn cancel_external_workflow(
&self,
_ctx: WorkflowInterceptorContext,
input: CancelExternalWorkflowInput,
next: WorkflowNext<
'static,
CancelExternalWorkflowInput,
WorkflowOutboundFuture<CancelExternalWfResult>,
>,
) -> WorkflowOutboundFuture<CancelExternalWfResult> {
next.run(input)
}
fn continue_as_new(
&self,
_ctx: SyncWorkflowInterceptorContext,
input: ContinueAsNewInput,
next: WorkflowNext<'static, ContinueAsNewInput, ContinueAsNewResult>,
) -> ContinueAsNewResult {
next.run(input)
}
fn start_nexus_operation(
&self,
_ctx: WorkflowInterceptorContext,
input: StartNexusOperationInput,
next: WorkflowNext<
'static,
StartNexusOperationInput,
CancellableWorkflowOutboundFuture<StartNexusOperationResult>,
>,
) -> CancellableWorkflowOutboundFuture<StartNexusOperationResult> {
next.run(input)
}
}
macro_rules! outbound_chain {
($fn_name:ident, $method:ident, $context:ty, $input:ty, $output:ty) => {
pub(crate) fn $fn_name(
interceptors: Rc<[Arc<dyn WorkflowInterceptor>]>,
ctx: $context,
input: $input,
next: WorkflowNext<'static, $input, $output>,
) -> $output {
fn call(
interceptors: Rc<[Arc<dyn WorkflowInterceptor>]>,
interceptor_count: usize,
ctx: $context,
input: $input,
next: WorkflowNext<'static, $input, $output>,
) -> $output {
if let Some(interceptor_index) = interceptor_count.checked_sub(1) {
let interceptor = interceptors[interceptor_index].clone();
let next_ctx = ctx.clone();
let downstream = WorkflowNext::new(move |input| {
call(interceptors, interceptor_index, next_ctx, input, next)
});
interceptor.$method(ctx, input, downstream)
} else {
next.run(input)
}
}
let interceptor_count = interceptors.len();
call(interceptors, interceptor_count, ctx, input, next)
}
};
}
outbound_chain!(
call_start_timer,
start_timer,
WorkflowInterceptorContext,
StartTimerInput,
CancellableWorkflowOutboundFuture<TimerResult>
);
outbound_chain!(
call_schedule_activity,
schedule_activity,
WorkflowInterceptorContext,
ScheduleActivityInput,
CancellableWorkflowOutboundFuture<ScheduleActivityResult>
);
outbound_chain!(
call_schedule_local_activity,
schedule_local_activity,
WorkflowInterceptorContext,
ScheduleLocalActivityInput,
CancellableWorkflowOutboundFuture<ScheduleActivityResult>
);
outbound_chain!(
call_start_child_workflow,
start_child_workflow,
WorkflowInterceptorContext,
StartChildWorkflowInput,
CancellableWorkflowOutboundFuture<StartChildWorkflowResult>
);
outbound_chain!(
call_signal_workflow,
signal_workflow,
WorkflowInterceptorContext,
SignalWorkflowInput,
CancellableWorkflowOutboundFuture<SignalWorkflowResult>
);
outbound_chain!(
call_cancel_external_workflow,
cancel_external_workflow,
WorkflowInterceptorContext,
CancelExternalWorkflowInput,
WorkflowOutboundFuture<CancelExternalWfResult>
);
outbound_chain!(
call_continue_as_new,
continue_as_new,
SyncWorkflowInterceptorContext,
ContinueAsNewInput,
ContinueAsNewResult
);
outbound_chain!(
call_start_nexus_operation,
start_nexus_operation,
WorkflowInterceptorContext,
StartNexusOperationInput,
CancellableWorkflowOutboundFuture<StartNexusOperationResult>
);
type WorkflowInterceptorConstructorFn =
dyn Fn(&WorkflowContextView) -> Arc<dyn WorkflowInterceptor> + Send + Sync + 'static;
#[derive(Clone)]
pub struct WorkflowInterceptorConstructor {
constructor: Arc<WorkflowInterceptorConstructorFn>,
}
impl WorkflowInterceptorConstructor {
pub fn new<F, I>(constructor: F) -> Self
where
F: Fn(&WorkflowContextView) -> I + Send + Sync + 'static,
I: WorkflowInterceptor,
{
Self {
constructor: Arc::new(move |ctx| Arc::new(constructor(ctx))),
}
}
pub(crate) fn construct(&self, ctx: &WorkflowContextView) -> Arc<dyn WorkflowInterceptor> {
(self.constructor)(ctx)
}
}
pub(crate) fn wrong_workflow_input_type(type_name: &'static str) -> WorkflowTermination {
WorkflowTermination::failed_application(temporalio_common_wasm::error::ApplicationFailure::new(
anyhow::anyhow!(
"Workflow inbound interceptor returned arguments with wrong concrete type for workflow {type_name}"
),
))
}