use std::{collections::HashMap, time::Duration};
use crate::{MemoValues, runtime::types::ContinueAsNewRequest};
use temporalio_common_wasm::{
Priority, RetryPolicy,
data_converters::{
GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
SerializationContextData,
},
protos::{
coresdk::{
child_workflow::{
ChildWorkflowCancellationType as ProtoChildWorkflowCancellationType,
ParentClosePolicy as ProtoParentClosePolicy,
},
common::VersioningIntent as ProtoVersioningIntent,
nexus::NexusOperationCancellationType as ProtoNexusOperationCancellationType,
workflow_activation::SignalWorkflow,
workflow_commands::{
ActivityCancellationType as ProtoActivityCancellationType,
ContinueAsNewWorkflowExecution, ScheduleActivity, ScheduleLocalActivity,
ScheduleNexusOperation, StartChildWorkflowExecution, StartTimer, WorkflowCommand,
workflow_command,
},
},
temporal::api::{
common::v1::Payload,
enums::v1::{
ContinueAsNewVersioningBehavior as ProtoContinueAsNewVersioningBehavior,
WorkflowIdReusePolicy as ProtoWorkflowIdReusePolicy,
},
sdk::v1::UserMetadata,
},
},
search_attributes::SearchAttributes,
};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum ActivityCancellationType {
#[default]
TryCancel,
WaitCancellationCompleted,
Abandon,
}
impl From<ActivityCancellationType> for ProtoActivityCancellationType {
fn from(value: ActivityCancellationType) -> Self {
match value {
ActivityCancellationType::TryCancel => Self::TryCancel,
ActivityCancellationType::WaitCancellationCompleted => Self::WaitCancellationCompleted,
ActivityCancellationType::Abandon => Self::Abandon,
}
}
}
impl From<ProtoActivityCancellationType> for ActivityCancellationType {
fn from(value: ProtoActivityCancellationType) -> Self {
match value {
ProtoActivityCancellationType::TryCancel => Self::TryCancel,
ProtoActivityCancellationType::WaitCancellationCompleted => {
Self::WaitCancellationCompleted
}
ProtoActivityCancellationType::Abandon => Self::Abandon,
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum ChildWorkflowCancellationType {
Abandon,
TryCancel,
#[default]
WaitCancellationCompleted,
WaitCancellationRequested,
}
impl From<ChildWorkflowCancellationType> for ProtoChildWorkflowCancellationType {
fn from(value: ChildWorkflowCancellationType) -> Self {
match value {
ChildWorkflowCancellationType::Abandon => Self::Abandon,
ChildWorkflowCancellationType::TryCancel => Self::TryCancel,
ChildWorkflowCancellationType::WaitCancellationCompleted => {
Self::WaitCancellationCompleted
}
ChildWorkflowCancellationType::WaitCancellationRequested => {
Self::WaitCancellationRequested
}
}
}
}
impl From<ProtoChildWorkflowCancellationType> for ChildWorkflowCancellationType {
fn from(value: ProtoChildWorkflowCancellationType) -> Self {
match value {
ProtoChildWorkflowCancellationType::Abandon => Self::Abandon,
ProtoChildWorkflowCancellationType::TryCancel => Self::TryCancel,
ProtoChildWorkflowCancellationType::WaitCancellationCompleted => {
Self::WaitCancellationCompleted
}
ProtoChildWorkflowCancellationType::WaitCancellationRequested => {
Self::WaitCancellationRequested
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum ParentClosePolicy {
#[default]
Unspecified,
Terminate,
Abandon,
RequestCancel,
}
impl From<ParentClosePolicy> for ProtoParentClosePolicy {
fn from(value: ParentClosePolicy) -> Self {
match value {
ParentClosePolicy::Unspecified => Self::Unspecified,
ParentClosePolicy::Terminate => Self::Terminate,
ParentClosePolicy::Abandon => Self::Abandon,
ParentClosePolicy::RequestCancel => Self::RequestCancel,
}
}
}
impl From<ProtoParentClosePolicy> for ParentClosePolicy {
fn from(value: ProtoParentClosePolicy) -> Self {
match value {
ProtoParentClosePolicy::Unspecified => Self::Unspecified,
ProtoParentClosePolicy::Terminate => Self::Terminate,
ProtoParentClosePolicy::Abandon => Self::Abandon,
ProtoParentClosePolicy::RequestCancel => Self::RequestCancel,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum WorkflowIdReusePolicy {
#[default]
Unspecified,
AllowDuplicate,
AllowDuplicateFailedOnly,
RejectDuplicate,
TerminateIfRunning,
}
impl From<WorkflowIdReusePolicy> for ProtoWorkflowIdReusePolicy {
#[allow(deprecated)]
fn from(value: WorkflowIdReusePolicy) -> Self {
match value {
WorkflowIdReusePolicy::Unspecified => Self::Unspecified,
WorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
WorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
WorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
WorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
}
}
}
impl From<ProtoWorkflowIdReusePolicy> for WorkflowIdReusePolicy {
#[allow(deprecated)]
fn from(value: ProtoWorkflowIdReusePolicy) -> Self {
match value {
ProtoWorkflowIdReusePolicy::Unspecified => Self::Unspecified,
ProtoWorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
ProtoWorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
ProtoWorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
ProtoWorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum VersioningIntent {
#[default]
Unspecified,
Compatible,
Default,
}
impl From<VersioningIntent> for ProtoVersioningIntent {
fn from(value: VersioningIntent) -> Self {
match value {
VersioningIntent::Unspecified => Self::Unspecified,
VersioningIntent::Compatible => Self::Compatible,
VersioningIntent::Default => Self::Default,
}
}
}
impl From<ProtoVersioningIntent> for VersioningIntent {
fn from(value: ProtoVersioningIntent) -> Self {
match value {
ProtoVersioningIntent::Unspecified => Self::Unspecified,
ProtoVersioningIntent::Compatible => Self::Compatible,
ProtoVersioningIntent::Default => Self::Default,
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum NexusOperationCancellationType {
#[default]
WaitCancellationCompleted,
Abandon,
TryCancel,
WaitCancellationRequested,
}
impl From<NexusOperationCancellationType> for ProtoNexusOperationCancellationType {
fn from(value: NexusOperationCancellationType) -> Self {
match value {
NexusOperationCancellationType::WaitCancellationCompleted => {
Self::WaitCancellationCompleted
}
NexusOperationCancellationType::Abandon => Self::Abandon,
NexusOperationCancellationType::TryCancel => Self::TryCancel,
NexusOperationCancellationType::WaitCancellationRequested => {
Self::WaitCancellationRequested
}
}
}
}
impl From<ProtoNexusOperationCancellationType> for NexusOperationCancellationType {
fn from(value: ProtoNexusOperationCancellationType) -> Self {
match value {
ProtoNexusOperationCancellationType::WaitCancellationCompleted => {
Self::WaitCancellationCompleted
}
ProtoNexusOperationCancellationType::Abandon => Self::Abandon,
ProtoNexusOperationCancellationType::TryCancel => Self::TryCancel,
ProtoNexusOperationCancellationType::WaitCancellationRequested => {
Self::WaitCancellationRequested
}
}
}
}
#[derive(Debug, bon::Builder, Clone)]
#[non_exhaustive]
#[builder(start_fn = with_close_timeouts, on(String, into), state_mod(vis = "pub"))]
pub struct ActivityOptions {
#[builder(start_fn)]
pub close_timeouts: ActivityCloseTimeouts,
pub activity_id: Option<String>,
pub task_queue: Option<String>,
pub schedule_to_start_timeout: Option<Duration>,
pub heartbeat_timeout: Option<Duration>,
#[builder(default, into)]
pub cancellation_type: ActivityCancellationType,
#[builder(into)]
pub retry_policy: Option<RetryPolicy>,
pub summary: Option<String>,
pub priority: Option<Priority>,
#[builder(default)]
pub do_not_eagerly_execute: bool,
}
impl ActivityOptions {
pub fn with_start_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
Self::with_close_timeouts(ActivityCloseTimeouts::StartToClose(duration))
}
pub fn with_schedule_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
Self::with_close_timeouts(ActivityCloseTimeouts::ScheduleToClose(duration))
}
pub fn start_to_close_timeout(duration: Duration) -> Self {
Self::with_start_to_close_timeout(duration).build()
}
pub fn schedule_to_close_timeout(duration: Duration) -> Self {
Self::with_schedule_to_close_timeout(duration).build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivityCloseTimeouts {
ScheduleToClose(Duration),
StartToClose(Duration),
Both {
start_to_close: Duration,
schedule_to_close: Duration,
},
}
impl ActivityCloseTimeouts {
fn into_durations(self) -> (Option<Duration>, Option<Duration>) {
match self {
Self::ScheduleToClose(schedule_to_close) => (None, Some(schedule_to_close)),
Self::StartToClose(start_to_close) => (Some(start_to_close), None),
Self::Both {
start_to_close,
schedule_to_close,
} => (Some(start_to_close), Some(schedule_to_close)),
}
}
}
impl ActivityOptions {
pub(crate) fn into_command(
self,
seq: u32,
activity_type: String,
args: Vec<Payload>,
headers: HashMap<String, Payload>,
) -> WorkflowCommand {
let (start_to_close_timeout, schedule_to_close_timeout) =
self.close_timeouts.into_durations();
command_with_metadata(
workflow_command::Variant::ScheduleActivity(ScheduleActivity {
seq,
activity_type,
activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
task_queue: self.task_queue.unwrap_or_default(),
arguments: args,
headers,
schedule_to_close_timeout: schedule_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
schedule_to_start_timeout: self
.schedule_to_start_timeout
.and_then(|duration| duration.try_into().ok()),
start_to_close_timeout: start_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
heartbeat_timeout: self
.heartbeat_timeout
.and_then(|duration| duration.try_into().ok()),
cancellation_type: ProtoActivityCancellationType::from(self.cancellation_type)
.into(),
retry_policy: self.retry_policy.map(Into::into),
priority: self.priority.map(Into::into),
do_not_eagerly_execute: self.do_not_eagerly_execute,
..Default::default()
}),
self.summary,
None,
)
}
}
#[derive(Default, Debug, Clone)]
pub struct LocalActivityOptions {
pub activity_id: Option<String>,
pub retry_policy: RetryPolicy,
pub attempt: Option<u32>,
pub original_schedule_time: Option<prost_types::Timestamp>,
pub timer_backoff_threshold: Option<Duration>,
pub cancel_type: ActivityCancellationType,
pub schedule_to_close_timeout: Option<Duration>,
pub schedule_to_start_timeout: Option<Duration>,
pub start_to_close_timeout: Option<Duration>,
pub summary: Option<String>,
}
impl LocalActivityOptions {
pub(crate) fn into_command(
mut self,
seq: u32,
activity_type: String,
args: Vec<Payload>,
headers: HashMap<String, Payload>,
) -> WorkflowCommand {
self.schedule_to_close_timeout
.get_or_insert(Duration::from_secs(100));
command_with_metadata(
workflow_command::Variant::ScheduleLocalActivity(ScheduleLocalActivity {
seq,
activity_type,
activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
arguments: args,
headers,
retry_policy: Some(self.retry_policy.into()),
attempt: self.attempt.unwrap_or(1),
original_schedule_time: self.original_schedule_time,
local_retry_threshold: self
.timer_backoff_threshold
.and_then(|duration| duration.try_into().ok()),
cancellation_type: ProtoActivityCancellationType::from(self.cancel_type).into(),
schedule_to_close_timeout: self
.schedule_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
schedule_to_start_timeout: self
.schedule_to_start_timeout
.and_then(|duration| duration.try_into().ok()),
start_to_close_timeout: self
.start_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
}),
self.summary,
None,
)
}
}
#[derive(Default, Debug, Clone, bon::Builder)]
#[non_exhaustive]
pub struct ChildWorkflowOptions {
pub workflow_id: Option<String>,
pub task_queue: Option<String>,
#[builder(default)]
pub cancel_type: ChildWorkflowCancellationType,
#[builder(default)]
pub parent_close_policy: ParentClosePolicy,
pub static_summary: Option<String>,
pub static_details: Option<String>,
#[builder(default)]
pub id_reuse_policy: WorkflowIdReusePolicy,
pub execution_timeout: Option<Duration>,
pub run_timeout: Option<Duration>,
pub task_timeout: Option<Duration>,
pub cron_schedule: Option<String>,
pub search_attributes: Option<SearchAttributes>,
pub priority: Option<Priority>,
}
impl ChildWorkflowOptions {
pub fn workflow_id(workflow_id: String) -> Self {
Self::builder().workflow_id(workflow_id).build()
}
pub(crate) fn into_command(
self,
seq: u32,
workflow_type: String,
args: Vec<Payload>,
headers: HashMap<String, Payload>,
workflow_id: String,
) -> WorkflowCommand {
command_with_metadata(
workflow_command::Variant::StartChildWorkflowExecution(StartChildWorkflowExecution {
seq,
workflow_type,
workflow_id,
task_queue: self.task_queue.unwrap_or_default(),
input: args,
headers,
cancellation_type: ProtoChildWorkflowCancellationType::from(self.cancel_type)
.into(),
parent_close_policy: ProtoParentClosePolicy::from(self.parent_close_policy).into(),
workflow_id_reuse_policy: ProtoWorkflowIdReusePolicy::from(
match self.id_reuse_policy {
WorkflowIdReusePolicy::Unspecified => WorkflowIdReusePolicy::AllowDuplicate,
policy => policy,
},
)
.into(),
workflow_execution_timeout: self
.execution_timeout
.and_then(|duration| duration.try_into().ok()),
workflow_run_timeout: self
.run_timeout
.and_then(|duration| duration.try_into().ok()),
workflow_task_timeout: self
.task_timeout
.and_then(|duration| duration.try_into().ok()),
cron_schedule: self.cron_schedule.unwrap_or_default(),
search_attributes: self.search_attributes.map(|t| t.into_proto()),
priority: self.priority.map(Into::into),
..Default::default()
}),
self.static_summary,
self.static_details,
)
}
}
#[derive(Debug)]
pub struct Signal {
pub signal_name: String,
pub data: SignalData,
}
impl Signal {
pub fn new(
name: impl Into<String>,
input: impl IntoIterator<Item = impl Into<Payload>>,
) -> Self {
Self {
signal_name: name.into(),
data: SignalData::new(input),
}
}
pub(crate) fn into_invocation(self) -> SignalWorkflow {
SignalWorkflow {
signal_name: self.signal_name,
input: self.data.input,
identity: String::new(),
headers: self.data.headers,
}
}
}
#[derive(Default, Debug)]
pub struct SignalData {
pub input: Vec<Payload>,
pub headers: HashMap<String, Payload>,
}
impl SignalData {
pub fn new(input: impl IntoIterator<Item = impl Into<Payload>>) -> Self {
Self {
input: input.into_iter().map(Into::into).collect(),
headers: HashMap::new(),
}
}
pub fn with_header(
&mut self,
key: impl Into<String>,
payload: impl Into<Payload>,
) -> &mut Self {
self.headers.insert(key.into(), payload.into());
self
}
}
#[derive(Default, Debug, Clone)]
pub struct TimerOptions {
pub duration: Duration,
pub summary: Option<String>,
}
impl From<Duration> for TimerOptions {
fn from(duration: Duration) -> Self {
TimerOptions {
duration,
..Default::default()
}
}
}
impl TimerOptions {
pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
command_with_metadata(
workflow_command::Variant::StartTimer(StartTimer {
seq,
start_to_fire_timeout: Some(
self.duration
.try_into()
.expect("workflow timer timeout must fit into protobuf duration"),
),
}),
self.summary,
None,
)
}
}
#[derive(Default, Debug, Clone)]
pub struct NexusOperationOptions {
pub endpoint: String,
pub service: String,
pub operation: String,
pub input: Option<Payload>,
pub schedule_to_close_timeout: Option<Duration>,
pub nexus_header: HashMap<String, String>,
pub cancellation_type: Option<NexusOperationCancellationType>,
pub schedule_to_start_timeout: Option<Duration>,
pub start_to_close_timeout: Option<Duration>,
}
impl NexusOperationOptions {
pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
workflow_command::Variant::ScheduleNexusOperation(ScheduleNexusOperation {
seq,
endpoint: self.endpoint,
service: self.service,
operation: self.operation,
input: self.input,
schedule_to_close_timeout: self
.schedule_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
schedule_to_start_timeout: self
.schedule_to_start_timeout
.and_then(|duration| duration.try_into().ok()),
start_to_close_timeout: self
.start_to_close_timeout
.and_then(|duration| duration.try_into().ok()),
nexus_header: self.nexus_header,
cancellation_type: ProtoNexusOperationCancellationType::from(
self.cancellation_type
.unwrap_or(NexusOperationCancellationType::WaitCancellationCompleted),
)
.into(),
})
.into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum ContinueAsNewVersioningBehavior {
#[default]
Unspecified,
AutoUpgrade,
UseRampingVersion,
}
impl From<ContinueAsNewVersioningBehavior> for ProtoContinueAsNewVersioningBehavior {
fn from(value: ContinueAsNewVersioningBehavior) -> Self {
match value {
ContinueAsNewVersioningBehavior::Unspecified => {
ProtoContinueAsNewVersioningBehavior::Unspecified
}
ContinueAsNewVersioningBehavior::AutoUpgrade => {
ProtoContinueAsNewVersioningBehavior::AutoUpgrade
}
ContinueAsNewVersioningBehavior::UseRampingVersion => {
ProtoContinueAsNewVersioningBehavior::UseRampingVersion
}
}
}
}
impl From<ProtoContinueAsNewVersioningBehavior> for ContinueAsNewVersioningBehavior {
fn from(value: ProtoContinueAsNewVersioningBehavior) -> Self {
match value {
ProtoContinueAsNewVersioningBehavior::Unspecified => {
ContinueAsNewVersioningBehavior::Unspecified
}
ProtoContinueAsNewVersioningBehavior::AutoUpgrade => {
ContinueAsNewVersioningBehavior::AutoUpgrade
}
ProtoContinueAsNewVersioningBehavior::UseRampingVersion => {
ContinueAsNewVersioningBehavior::UseRampingVersion
}
}
}
}
#[derive(Default, Debug, bon::Builder)]
#[non_exhaustive]
pub struct ContinueAsNewOptions {
pub workflow_type: Option<String>,
pub task_queue: Option<String>,
pub run_timeout: Option<Duration>,
pub task_timeout: Option<Duration>,
pub backoff_start_interval: Option<Duration>,
pub memo: Option<MemoValues>,
pub search_attributes: Option<SearchAttributes>,
#[builder(into)]
pub retry_policy: Option<RetryPolicy>,
pub versioning_intent: Option<VersioningIntent>,
pub initial_versioning_behavior: Option<ContinueAsNewVersioningBehavior>,
}
impl ContinueAsNewOptions {
pub(crate) fn into_request(
self,
workflow_type: String,
arguments: Vec<Payload>,
headers: HashMap<String, Payload>,
payload_converter: &PayloadConverter,
) -> Result<ContinueAsNewRequest, PayloadConversionError> {
let memo = self
.memo
.map(|memo| memo.encode(payload_converter))
.transpose()?
.unwrap_or_default();
Ok(ContinueAsNewWorkflowExecution {
workflow_type: self.workflow_type.unwrap_or(workflow_type),
task_queue: self.task_queue.unwrap_or_default(),
arguments,
workflow_run_timeout: self
.run_timeout
.and_then(|duration| duration.try_into().ok()),
workflow_task_timeout: self
.task_timeout
.and_then(|duration| duration.try_into().ok()),
backoff_start_interval: self
.backoff_start_interval
.and_then(|duration| duration.try_into().ok()),
memo,
headers,
search_attributes: self.search_attributes.map(|t| t.into_proto()),
retry_policy: self.retry_policy.map(Into::into),
versioning_intent: ProtoVersioningIntent::from(
self.versioning_intent
.unwrap_or(VersioningIntent::Unspecified),
)
.into(),
initial_versioning_behavior: ProtoContinueAsNewVersioningBehavior::from(
self.initial_versioning_behavior
.unwrap_or(ContinueAsNewVersioningBehavior::Unspecified),
)
.into(),
})
}
}
fn command_with_metadata(
variant: workflow_command::Variant,
summary: Option<String>,
details: Option<String>,
) -> WorkflowCommand {
WorkflowCommand {
variant: Some(variant),
user_metadata: string_user_metadata(summary, details),
}
}
fn string_user_metadata(summary: Option<String>, details: Option<String>) -> Option<UserMetadata> {
if summary.is_none() && details.is_none() {
return None;
}
let converter = PayloadConverter::default();
let context = SerializationContext {
data: &SerializationContextData::Workflow,
converter: &converter,
};
Some(UserMetadata {
summary: summary.map(|value| {
converter
.to_payload(&context, &value)
.expect("String-to-JSON payload serialization is infallible")
}),
details: details.map(|value| {
converter
.to_payload(&context, &value)
.expect("String-to-JSON payload serialization is infallible")
}),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn activity_cancellation_default_preserves_sdk_behavior() {
assert_eq!(
ActivityCancellationType::default(),
ActivityCancellationType::TryCancel
);
}
#[test]
fn child_workflow_cancellation_defaults_to_wait_for_completion() {
assert_eq!(
ChildWorkflowOptions::default().cancel_type,
ChildWorkflowCancellationType::WaitCancellationCompleted
);
let command = ChildWorkflowOptions::default().into_command(
1,
"child".to_string(),
vec![],
HashMap::new(),
"child-id".to_string(),
);
let Some(workflow_command::Variant::StartChildWorkflowExecution(command)) = command.variant
else {
panic!("expected StartChildWorkflowExecution command");
};
assert_eq!(
command.cancellation_type,
ProtoChildWorkflowCancellationType::WaitCancellationCompleted as i32
);
}
#[test]
fn other_policy_defaults_preserve_sdk_behavior() {
assert_eq!(ParentClosePolicy::default(), ParentClosePolicy::Unspecified);
assert_eq!(
WorkflowIdReusePolicy::default(),
WorkflowIdReusePolicy::Unspecified
);
assert_eq!(VersioningIntent::default(), VersioningIntent::Unspecified);
assert_eq!(
NexusOperationCancellationType::default(),
NexusOperationCancellationType::WaitCancellationCompleted
);
}
#[test]
fn continue_as_new_options_maps_backoff_start_interval_to_request() {
let req = ContinueAsNewOptions {
backoff_start_interval: Some(Duration::from_secs(7)),
versioning_intent: Some(VersioningIntent::Compatible),
..Default::default()
}
.into_request(
"test-workflow".to_string(),
vec![],
HashMap::new(),
&PayloadConverter::default(),
)
.unwrap();
let backoff = req
.backoff_start_interval
.expect("backoff_start_interval should be set");
assert_eq!(backoff.seconds, 7);
assert_eq!(backoff.nanos, 0);
assert_eq!(
req.versioning_intent,
ProtoVersioningIntent::Compatible as i32
);
}
#[test]
fn activity_options_with_start_to_close_timeout_wrapper_supports_builder_chaining() {
let opts = ActivityOptions::with_start_to_close_timeout(Duration::from_secs(5))
.heartbeat_timeout(Duration::from_secs(2))
.build();
assert_eq!(
opts.close_timeouts,
ActivityCloseTimeouts::StartToClose(Duration::from_secs(5))
);
assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
}
#[test]
fn activity_options_with_schedule_to_close_timeout_wrapper_supports_builder_chaining() {
let opts = ActivityOptions::with_schedule_to_close_timeout(Duration::from_secs(5))
.heartbeat_timeout(Duration::from_secs(2))
.build();
assert_eq!(
opts.close_timeouts,
ActivityCloseTimeouts::ScheduleToClose(Duration::from_secs(5))
);
assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
}
#[test]
fn activity_options_both_close_timeouts_map_to_command() {
let req = ActivityOptions::with_close_timeouts(ActivityCloseTimeouts::Both {
start_to_close: Duration::from_secs(3),
schedule_to_close: Duration::from_secs(8),
})
.cancellation_type(ActivityCancellationType::Abandon)
.build()
.into_command(7, "test".to_string(), vec![], HashMap::new());
let Some(workflow_command::Variant::ScheduleActivity(req)) = req.variant else {
panic!("expected ScheduleActivity command");
};
assert_eq!(req.start_to_close_timeout.unwrap().seconds, 3);
assert_eq!(req.schedule_to_close_timeout.unwrap().seconds, 8);
assert_eq!(
req.cancellation_type,
ProtoActivityCancellationType::Abandon as i32
);
}
#[test]
fn child_workflow_run_timeout_uses_run_timeout_field() {
let opts = ChildWorkflowOptions {
workflow_id: Some("test-wf".to_string()),
cancel_type: ChildWorkflowCancellationType::WaitCancellationRequested,
parent_close_policy: ParentClosePolicy::RequestCancel,
id_reuse_policy: WorkflowIdReusePolicy::RejectDuplicate,
execution_timeout: Some(Duration::from_secs(60)),
run_timeout: Some(Duration::from_secs(10)),
..Default::default()
};
let command = opts.into_command(
1,
"TestWorkflow".to_string(),
vec![],
HashMap::new(),
"test-wf".into(),
);
let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
else {
panic!("expected StartChildWorkflowExecution command");
};
let exec_timeout = req.workflow_execution_timeout.unwrap();
let run_timeout = req.workflow_run_timeout.unwrap();
assert_eq!(exec_timeout.seconds, 60);
assert_eq!(run_timeout.seconds, 10);
assert_eq!(
req.cancellation_type,
ProtoChildWorkflowCancellationType::WaitCancellationRequested as i32
);
assert_eq!(
req.parent_close_policy,
ProtoParentClosePolicy::RequestCancel as i32
);
assert_eq!(
req.workflow_id_reuse_policy,
ProtoWorkflowIdReusePolicy::RejectDuplicate as i32
);
}
#[test]
fn child_workflow_run_timeout_none_when_unset() {
let opts = ChildWorkflowOptions {
workflow_id: Some("test-wf".to_string()),
execution_timeout: Some(Duration::from_secs(60)),
..Default::default()
};
let command = opts.into_command(
1,
"TestWorkflow".to_string(),
vec![],
HashMap::new(),
"test-wf".into(),
);
let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
else {
panic!("expected StartChildWorkflowExecution command");
};
let exec_timeout = req.workflow_execution_timeout.unwrap();
assert_eq!(exec_timeout.seconds, 60);
assert!(req.workflow_run_timeout.is_none());
}
}