1use std::{collections::HashMap, time::Duration};
2
3use crate::{MemoValues, WorkflowCancellationToken, runtime::types::ContinueAsNewRequest};
4#[cfg(feature = "experimental")]
5use temporalio_common_wasm::protos::temporal::api::enums::v1::ContinueAsNewVersioningBehavior as ProtoContinueAsNewVersioningBehavior;
6use temporalio_common_wasm::{
7 ActivityCloseTimeouts, Priority, RetryPolicy,
8 data_converters::{
9 GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
10 SerializationContextData, WorkflowSerializationContext,
11 },
12 protos::{
13 coresdk::{
14 child_workflow::{
15 ChildWorkflowCancellationType as ProtoChildWorkflowCancellationType,
16 ParentClosePolicy as ProtoParentClosePolicy,
17 },
18 common::VersioningIntent as ProtoVersioningIntent,
19 workflow_commands::{
20 ActivityCancellationType as ProtoActivityCancellationType,
21 ContinueAsNewWorkflowExecution, ScheduleActivity, ScheduleLocalActivity,
22 SignalExternalWorkflowExecution, StartChildWorkflowExecution, StartTimer,
23 WorkflowCommand, signal_external_workflow_execution, workflow_command,
24 },
25 },
26 temporal::api::{
27 common::v1::Payload,
28 enums::v1::WorkflowIdReusePolicy as ProtoWorkflowIdReusePolicy,
29 sdk::v1::{EventGroupMarker, UserMetadata},
30 },
31 },
32 search_attributes::SearchAttributes,
33};
34
35#[cfg(feature = "experimental")]
36mod continue_as_new_versioning;
37#[cfg(feature = "experimental")]
38mod nexus;
39
40#[cfg(feature = "experimental")]
41pub use continue_as_new_versioning::ContinueAsNewVersioningBehavior;
42#[cfg(feature = "experimental")]
43pub use nexus::{NexusOperationCancellationType, NexusOperationOptions};
44
45#[derive(
47 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
48)]
49#[non_exhaustive]
50pub enum ActivityCancellationType {
51 #[default]
53 TryCancel,
54 WaitCancellationCompleted,
56 Abandon,
58}
59
60impl From<ActivityCancellationType> for ProtoActivityCancellationType {
61 fn from(value: ActivityCancellationType) -> Self {
62 match value {
63 ActivityCancellationType::TryCancel => Self::TryCancel,
64 ActivityCancellationType::WaitCancellationCompleted => Self::WaitCancellationCompleted,
65 ActivityCancellationType::Abandon => Self::Abandon,
66 }
67 }
68}
69
70impl From<ProtoActivityCancellationType> for ActivityCancellationType {
71 fn from(value: ProtoActivityCancellationType) -> Self {
72 match value {
73 ProtoActivityCancellationType::TryCancel => Self::TryCancel,
74 ProtoActivityCancellationType::WaitCancellationCompleted => {
75 Self::WaitCancellationCompleted
76 }
77 ProtoActivityCancellationType::Abandon => Self::Abandon,
78 }
79 }
80}
81
82#[derive(
84 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
85)]
86#[non_exhaustive]
87pub enum ChildWorkflowCancellationType {
88 Abandon,
90 TryCancel,
92 #[default]
94 WaitCancellationCompleted,
95 WaitCancellationRequested,
97}
98
99impl From<ChildWorkflowCancellationType> for ProtoChildWorkflowCancellationType {
100 fn from(value: ChildWorkflowCancellationType) -> Self {
101 match value {
102 ChildWorkflowCancellationType::Abandon => Self::Abandon,
103 ChildWorkflowCancellationType::TryCancel => Self::TryCancel,
104 ChildWorkflowCancellationType::WaitCancellationCompleted => {
105 Self::WaitCancellationCompleted
106 }
107 ChildWorkflowCancellationType::WaitCancellationRequested => {
108 Self::WaitCancellationRequested
109 }
110 }
111 }
112}
113
114impl From<ProtoChildWorkflowCancellationType> for ChildWorkflowCancellationType {
115 fn from(value: ProtoChildWorkflowCancellationType) -> Self {
116 match value {
117 ProtoChildWorkflowCancellationType::Abandon => Self::Abandon,
118 ProtoChildWorkflowCancellationType::TryCancel => Self::TryCancel,
119 ProtoChildWorkflowCancellationType::WaitCancellationCompleted => {
120 Self::WaitCancellationCompleted
121 }
122 ProtoChildWorkflowCancellationType::WaitCancellationRequested => {
123 Self::WaitCancellationRequested
124 }
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
131#[non_exhaustive]
132pub enum ParentClosePolicy {
133 #[default]
135 Unspecified,
136 Terminate,
138 Abandon,
140 RequestCancel,
142}
143
144impl From<ParentClosePolicy> for ProtoParentClosePolicy {
145 fn from(value: ParentClosePolicy) -> Self {
146 match value {
147 ParentClosePolicy::Unspecified => Self::Unspecified,
148 ParentClosePolicy::Terminate => Self::Terminate,
149 ParentClosePolicy::Abandon => Self::Abandon,
150 ParentClosePolicy::RequestCancel => Self::RequestCancel,
151 }
152 }
153}
154
155impl From<ProtoParentClosePolicy> for ParentClosePolicy {
156 fn from(value: ProtoParentClosePolicy) -> Self {
157 match value {
158 ProtoParentClosePolicy::Unspecified => Self::Unspecified,
159 ProtoParentClosePolicy::Terminate => Self::Terminate,
160 ProtoParentClosePolicy::Abandon => Self::Abandon,
161 ProtoParentClosePolicy::RequestCancel => Self::RequestCancel,
162 }
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
168#[non_exhaustive]
169pub enum WorkflowIdReusePolicy {
170 #[default]
172 Unspecified,
173 AllowDuplicate,
175 AllowDuplicateFailedOnly,
177 RejectDuplicate,
179 TerminateIfRunning,
181}
182
183impl From<WorkflowIdReusePolicy> for ProtoWorkflowIdReusePolicy {
184 #[allow(deprecated)]
185 fn from(value: WorkflowIdReusePolicy) -> Self {
186 match value {
187 WorkflowIdReusePolicy::Unspecified => Self::Unspecified,
188 WorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
189 WorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
190 WorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
191 WorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
192 }
193 }
194}
195
196impl From<ProtoWorkflowIdReusePolicy> for WorkflowIdReusePolicy {
197 #[allow(deprecated)]
198 fn from(value: ProtoWorkflowIdReusePolicy) -> Self {
199 match value {
200 ProtoWorkflowIdReusePolicy::Unspecified => Self::Unspecified,
201 ProtoWorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
202 ProtoWorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
203 ProtoWorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
204 ProtoWorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
211#[non_exhaustive]
212pub enum VersioningIntent {
213 #[default]
215 Unspecified,
216 Compatible,
218 Default,
220}
221
222impl From<VersioningIntent> for ProtoVersioningIntent {
223 fn from(value: VersioningIntent) -> Self {
224 match value {
225 VersioningIntent::Unspecified => Self::Unspecified,
226 VersioningIntent::Compatible => Self::Compatible,
227 VersioningIntent::Default => Self::Default,
228 }
229 }
230}
231
232impl From<ProtoVersioningIntent> for VersioningIntent {
233 fn from(value: ProtoVersioningIntent) -> Self {
234 match value {
235 ProtoVersioningIntent::Unspecified => Self::Unspecified,
236 ProtoVersioningIntent::Compatible => Self::Compatible,
237 ProtoVersioningIntent::Default => Self::Default,
238 }
239 }
240}
241
242#[derive(Debug, bon::Builder, Clone)]
244#[non_exhaustive]
245#[builder(start_fn = with_close_timeouts, on(String, into), state_mod(vis = "pub"))]
246pub struct ActivityOptions {
247 #[builder(start_fn)]
251 pub close_timeouts: ActivityCloseTimeouts,
252 pub activity_id: Option<String>,
258 pub task_queue: Option<String>,
262 pub schedule_to_start_timeout: Option<Duration>,
269 pub heartbeat_timeout: Option<Duration>,
272 #[builder(default, into)]
274 pub cancellation_type: ActivityCancellationType,
275 pub cancellation_token: Option<WorkflowCancellationToken>,
277 #[builder(into)]
279 pub retry_policy: Option<RetryPolicy>,
280 pub summary: Option<String>,
282 pub priority: Option<Priority>,
284 #[builder(default)]
286 pub do_not_eagerly_execute: bool,
287 #[cfg(feature = "experimental")]
292 #[builder(default)]
293 pub event_group_markers: Vec<EventGroupMarker>,
294}
295
296impl ActivityOptions {
297 pub fn with_start_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
299 Self::with_close_timeouts(ActivityCloseTimeouts::StartToClose(duration))
300 }
301
302 pub fn with_schedule_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
304 Self::with_close_timeouts(ActivityCloseTimeouts::ScheduleToClose(duration))
305 }
306
307 pub fn start_to_close_timeout(duration: Duration) -> Self {
311 Self::with_start_to_close_timeout(duration).build()
312 }
313
314 pub fn schedule_to_close_timeout(duration: Duration) -> Self {
318 Self::with_schedule_to_close_timeout(duration).build()
319 }
320}
321
322impl ActivityOptions {
323 pub(crate) fn into_command(
324 self,
325 seq: u32,
326 activity_type: String,
327 args: Vec<Payload>,
328 headers: HashMap<String, Payload>,
329 ) -> WorkflowCommand {
330 #[cfg(feature = "experimental")]
331 let event_group_markers = self.event_group_markers;
332 #[cfg(not(feature = "experimental"))]
333 let event_group_markers = Vec::new();
334 command_with_metadata(
335 workflow_command::Variant::ScheduleActivity(ScheduleActivity {
336 seq,
337 activity_type,
338 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
339 task_queue: self.task_queue.unwrap_or_default(),
340 arguments: args,
341 headers,
342 schedule_to_close_timeout: self
343 .close_timeouts
344 .schedule_to_close()
345 .and_then(|duration| duration.try_into().ok()),
346 schedule_to_start_timeout: self
347 .schedule_to_start_timeout
348 .and_then(|duration| duration.try_into().ok()),
349 start_to_close_timeout: self
350 .close_timeouts
351 .start_to_close()
352 .and_then(|duration| duration.try_into().ok()),
353 heartbeat_timeout: self
354 .heartbeat_timeout
355 .and_then(|duration| duration.try_into().ok()),
356 cancellation_type: ProtoActivityCancellationType::from(self.cancellation_type)
357 .into(),
358 retry_policy: self.retry_policy.map(Into::into),
359 priority: self.priority.map(Into::into),
360 do_not_eagerly_execute: self.do_not_eagerly_execute,
361 ..Default::default()
362 }),
363 self.summary,
364 None,
365 event_group_markers,
366 )
367 }
368}
369
370#[derive(Debug, Clone, bon::Builder)]
372#[non_exhaustive]
373pub struct LocalActivityOptions {
374 pub activity_id: Option<String>,
380 #[builder(default)]
382 pub retry_policy: RetryPolicy,
383 pub attempt: Option<u32>,
386 pub original_schedule_time: Option<prost_types::Timestamp>,
389 pub timer_backoff_threshold: Option<Duration>,
391 #[builder(default)]
393 pub cancel_type: ActivityCancellationType,
394 #[builder(default)]
399 pub include_arguments_in_marker: bool,
400 pub cancellation_token: Option<WorkflowCancellationToken>,
402 pub schedule_to_close_timeout: Option<Duration>,
406 pub schedule_to_start_timeout: Option<Duration>,
412 pub start_to_close_timeout: Option<Duration>,
417 pub summary: Option<String>,
419 #[cfg(feature = "experimental")]
424 #[builder(default)]
425 pub event_group_markers: Vec<EventGroupMarker>,
426}
427
428impl Default for LocalActivityOptions {
429 fn default() -> Self {
430 Self::builder().build()
431 }
432}
433
434impl LocalActivityOptions {
435 pub(crate) fn into_command(
436 mut self,
437 seq: u32,
438 activity_type: String,
439 args: Vec<Payload>,
440 headers: HashMap<String, Payload>,
441 ) -> WorkflowCommand {
442 self.schedule_to_close_timeout
445 .get_or_insert(Duration::from_secs(100));
446 #[cfg(feature = "experimental")]
447 let event_group_markers = self.event_group_markers;
448 #[cfg(not(feature = "experimental"))]
449 let event_group_markers = Vec::new();
450 command_with_metadata(
451 workflow_command::Variant::ScheduleLocalActivity(ScheduleLocalActivity {
452 seq,
453 activity_type,
454 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
455 arguments: args,
456 headers,
457 retry_policy: Some(self.retry_policy.into()),
458 attempt: self.attempt.unwrap_or(1),
459 original_schedule_time: self.original_schedule_time,
460 local_retry_threshold: self
461 .timer_backoff_threshold
462 .and_then(|duration| duration.try_into().ok()),
463 cancellation_type: ProtoActivityCancellationType::from(self.cancel_type).into(),
464 include_arguments_in_marker: self.include_arguments_in_marker,
465 schedule_to_close_timeout: self
466 .schedule_to_close_timeout
467 .and_then(|duration| duration.try_into().ok()),
468 schedule_to_start_timeout: self
469 .schedule_to_start_timeout
470 .and_then(|duration| duration.try_into().ok()),
471 start_to_close_timeout: self
472 .start_to_close_timeout
473 .and_then(|duration| duration.try_into().ok()),
474 }),
475 self.summary,
476 None,
477 event_group_markers,
478 )
479 }
480}
481
482#[derive(Default, Debug, Clone, bon::Builder)]
484#[non_exhaustive]
485pub struct ChildWorkflowOptions {
486 pub workflow_id: Option<String>,
488 pub task_queue: Option<String>,
492 #[builder(default)]
494 pub cancel_type: ChildWorkflowCancellationType,
495 pub cancellation_token: Option<WorkflowCancellationToken>,
497 #[builder(default)]
499 pub parent_close_policy: ParentClosePolicy,
500 pub static_summary: Option<String>,
502 pub static_details: Option<String>,
504 #[builder(default)]
506 pub id_reuse_policy: WorkflowIdReusePolicy,
507 pub execution_timeout: Option<Duration>,
509 pub run_timeout: Option<Duration>,
511 pub task_timeout: Option<Duration>,
513 pub cron_schedule: Option<String>,
515 pub search_attributes: Option<SearchAttributes>,
517 pub priority: Option<Priority>,
519 #[cfg(feature = "experimental")]
524 #[builder(default)]
525 pub event_group_markers: Vec<EventGroupMarker>,
526}
527
528impl ChildWorkflowOptions {
529 pub fn workflow_id(workflow_id: String) -> Self {
533 Self::builder().workflow_id(workflow_id).build()
534 }
535
536 pub(crate) fn into_command(
537 self,
538 seq: u32,
539 workflow_type: String,
540 args: Vec<Payload>,
541 headers: HashMap<String, Payload>,
542 workflow_id: String,
543 ) -> WorkflowCommand {
544 #[cfg(feature = "experimental")]
545 let event_group_markers = self.event_group_markers;
546 #[cfg(not(feature = "experimental"))]
547 let event_group_markers = Vec::new();
548 command_with_metadata(
549 workflow_command::Variant::StartChildWorkflowExecution(StartChildWorkflowExecution {
550 seq,
551 workflow_type,
552 workflow_id,
553 task_queue: self.task_queue.unwrap_or_default(),
554 input: args,
555 headers,
556 cancellation_type: ProtoChildWorkflowCancellationType::from(self.cancel_type)
557 .into(),
558 parent_close_policy: ProtoParentClosePolicy::from(self.parent_close_policy).into(),
559 workflow_id_reuse_policy: ProtoWorkflowIdReusePolicy::from(
560 match self.id_reuse_policy {
561 WorkflowIdReusePolicy::Unspecified => WorkflowIdReusePolicy::AllowDuplicate,
562 policy => policy,
563 },
564 )
565 .into(),
566 workflow_execution_timeout: self
567 .execution_timeout
568 .and_then(|duration| duration.try_into().ok()),
569 workflow_run_timeout: self
570 .run_timeout
571 .and_then(|duration| duration.try_into().ok()),
572 workflow_task_timeout: self
573 .task_timeout
574 .and_then(|duration| duration.try_into().ok()),
575 cron_schedule: self.cron_schedule.unwrap_or_default(),
576 search_attributes: self.search_attributes.map(|t| t.into_proto()),
577 priority: self.priority.map(Into::into),
578 ..Default::default()
579 }),
580 self.static_summary,
581 self.static_details,
582 event_group_markers,
583 )
584 }
585}
586
587#[derive(Debug, Clone, bon::Builder)]
589#[non_exhaustive]
590pub struct TimerOptions {
591 #[builder(start_fn)]
593 pub duration: Duration,
594 pub cancellation_token: Option<WorkflowCancellationToken>,
596 pub summary: Option<String>,
598 #[cfg(feature = "experimental")]
603 #[builder(default)]
604 pub event_group_markers: Vec<EventGroupMarker>,
605}
606
607impl Default for TimerOptions {
608 fn default() -> Self {
609 Self::builder(Duration::default()).build()
610 }
611}
612
613impl From<Duration> for TimerOptions {
614 fn from(duration: Duration) -> Self {
615 TimerOptions {
616 duration,
617 ..Default::default()
618 }
619 }
620}
621
622impl TimerOptions {
623 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
624 #[cfg(feature = "experimental")]
625 let event_group_markers = self.event_group_markers;
626 #[cfg(not(feature = "experimental"))]
627 let event_group_markers = Vec::new();
628 command_with_metadata(
629 workflow_command::Variant::StartTimer(StartTimer {
630 seq,
631 start_to_fire_timeout: Some(
632 self.duration
633 .try_into()
634 .expect("workflow timer timeout must fit into protobuf duration"),
635 ),
636 }),
637 self.summary,
638 None,
639 event_group_markers,
640 )
641 }
642}
643
644#[derive(Default, Debug, Clone, bon::Builder)]
646#[non_exhaustive]
647pub struct WaitConditionOptions {
648 pub cancellation_token: Option<WorkflowCancellationToken>,
650}
651
652#[derive(Default, Debug, Clone, bon::Builder)]
654#[non_exhaustive]
655pub struct SignalWorkflowOptions {
656 pub cancellation_token: Option<WorkflowCancellationToken>,
658 pub summary: Option<String>,
660 #[cfg(feature = "experimental")]
665 #[builder(default)]
666 pub event_group_markers: Vec<EventGroupMarker>,
667}
668
669impl SignalWorkflowOptions {
670 pub(crate) fn into_command(
671 self,
672 seq: u32,
673 signal_name: String,
674 args: Vec<Payload>,
675 headers: HashMap<String, Payload>,
676 target: signal_external_workflow_execution::Target,
677 ) -> WorkflowCommand {
678 #[cfg(feature = "experimental")]
679 let event_group_markers = self.event_group_markers;
680 #[cfg(not(feature = "experimental"))]
681 let event_group_markers = Vec::new();
682 command_with_metadata(
683 workflow_command::Variant::SignalExternalWorkflowExecution(
684 SignalExternalWorkflowExecution {
685 seq,
686 signal_name,
687 args,
688 target: Some(target),
689 headers,
690 },
691 ),
692 self.summary,
693 None,
694 event_group_markers,
695 )
696 }
697}
698
699#[derive(Default, Debug, bon::Builder)]
703#[non_exhaustive]
704pub struct ContinueAsNewOptions {
705 pub workflow_type: Option<String>,
707 pub task_queue: Option<String>,
709 pub run_timeout: Option<Duration>,
711 pub task_timeout: Option<Duration>,
713 pub backoff_start_interval: Option<Duration>,
715 pub memo: Option<MemoValues>,
717 pub search_attributes: Option<SearchAttributes>,
720 #[builder(into)]
722 pub retry_policy: Option<RetryPolicy>,
723 pub versioning_intent: Option<VersioningIntent>,
725 #[cfg(feature = "experimental")]
731 pub initial_versioning_behavior: Option<ContinueAsNewVersioningBehavior>,
732}
733
734impl ContinueAsNewOptions {
735 pub(crate) fn into_request(
736 self,
737 workflow_type: String,
738 arguments: Vec<Payload>,
739 headers: HashMap<String, Payload>,
740 payload_converter: &PayloadConverter,
741 ) -> Result<ContinueAsNewRequest, PayloadConversionError> {
742 let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
743 let context = SerializationContext::new(&context_data, payload_converter);
744 let memo = self
745 .memo
746 .map(|memo| {
747 memo.iter()
748 .map(|(key, value)| {
749 payload_converter
750 .to_payload(&context, value)
751 .map(|payload| (key.to_owned(), payload))
752 })
753 .collect::<Result<HashMap<_, _>, _>>()
754 })
755 .transpose()?
756 .unwrap_or_default();
757 #[cfg(feature = "experimental")]
758 let initial_versioning_behavior = ProtoContinueAsNewVersioningBehavior::from(
759 self.initial_versioning_behavior
760 .unwrap_or(ContinueAsNewVersioningBehavior::Unspecified),
761 )
762 .into();
763 #[cfg(not(feature = "experimental"))]
764 let initial_versioning_behavior = Default::default();
765 Ok(ContinueAsNewWorkflowExecution {
766 workflow_type: self.workflow_type.unwrap_or(workflow_type),
767 task_queue: self.task_queue.unwrap_or_default(),
768 arguments,
769 workflow_run_timeout: self
770 .run_timeout
771 .and_then(|duration| duration.try_into().ok()),
772 workflow_task_timeout: self
773 .task_timeout
774 .and_then(|duration| duration.try_into().ok()),
775 backoff_start_interval: self
776 .backoff_start_interval
777 .and_then(|duration| duration.try_into().ok()),
778 memo,
779 headers,
780 search_attributes: self.search_attributes.map(|t| t.into_proto()),
781 retry_policy: self.retry_policy.map(Into::into),
782 versioning_intent: ProtoVersioningIntent::from(
783 self.versioning_intent
784 .unwrap_or(VersioningIntent::Unspecified),
785 )
786 .into(),
787 initial_versioning_behavior,
788 })
789 }
790}
791
792fn command_with_metadata(
793 variant: workflow_command::Variant,
794 summary: Option<String>,
795 details: Option<String>,
796 markers: Vec<EventGroupMarker>,
797) -> WorkflowCommand {
798 WorkflowCommand {
799 variant: Some(variant),
800 user_metadata: string_user_metadata(summary, details),
801 event_group_markers: markers,
802 }
803}
804
805fn string_user_metadata(summary: Option<String>, details: Option<String>) -> Option<UserMetadata> {
806 if summary.is_none() && details.is_none() {
807 return None;
808 }
809 let converter = PayloadConverter::default();
810 let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
811 let context = SerializationContext::new(&context_data, &converter);
812 Some(UserMetadata {
813 summary: summary.map(|value| {
814 converter
815 .to_payload(&context, &value)
816 .expect("String-to-JSON payload serialization is infallible")
817 }),
818 details: details.map(|value| {
819 converter
820 .to_payload(&context, &value)
821 .expect("String-to-JSON payload serialization is infallible")
822 }),
823 })
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829
830 #[test]
831 fn activity_cancellation_default_preserves_sdk_behavior() {
832 assert_eq!(
833 ActivityCancellationType::default(),
834 ActivityCancellationType::TryCancel
835 );
836 }
837
838 #[test]
839 fn child_workflow_cancellation_defaults_to_wait_for_completion() {
840 assert_eq!(
841 ChildWorkflowOptions::default().cancel_type,
842 ChildWorkflowCancellationType::WaitCancellationCompleted
843 );
844 let command = ChildWorkflowOptions::default().into_command(
845 1,
846 "child".to_string(),
847 vec![],
848 HashMap::new(),
849 "child-id".to_string(),
850 );
851 let Some(workflow_command::Variant::StartChildWorkflowExecution(command)) = command.variant
852 else {
853 panic!("expected StartChildWorkflowExecution command");
854 };
855 assert_eq!(
856 command.cancellation_type,
857 ProtoChildWorkflowCancellationType::WaitCancellationCompleted as i32
858 );
859 }
860
861 #[test]
862 fn other_policy_defaults_preserve_sdk_behavior() {
863 assert_eq!(ParentClosePolicy::default(), ParentClosePolicy::Unspecified);
864 assert_eq!(
865 WorkflowIdReusePolicy::default(),
866 WorkflowIdReusePolicy::Unspecified
867 );
868 assert_eq!(VersioningIntent::default(), VersioningIntent::Unspecified);
869 }
870
871 #[test]
872 fn continue_as_new_options_maps_backoff_start_interval_to_request() {
873 let req = ContinueAsNewOptions {
874 backoff_start_interval: Some(Duration::from_secs(7)),
875 versioning_intent: Some(VersioningIntent::Compatible),
876 ..Default::default()
877 }
878 .into_request(
879 "test-workflow".to_string(),
880 vec![],
881 HashMap::new(),
882 &PayloadConverter::default(),
883 )
884 .unwrap();
885
886 let backoff = req
887 .backoff_start_interval
888 .expect("backoff_start_interval should be set");
889 assert_eq!(backoff.seconds, 7);
890 assert_eq!(backoff.nanos, 0);
891 assert_eq!(
892 req.versioning_intent,
893 ProtoVersioningIntent::Compatible as i32
894 );
895 }
896
897 #[test]
898 fn activity_options_with_start_to_close_timeout_wrapper_supports_builder_chaining() {
899 let opts = ActivityOptions::with_start_to_close_timeout(Duration::from_secs(5))
900 .heartbeat_timeout(Duration::from_secs(2))
901 .build();
902
903 assert_eq!(
904 opts.close_timeouts,
905 ActivityCloseTimeouts::StartToClose(Duration::from_secs(5))
906 );
907 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
908 }
909
910 #[test]
911 fn activity_options_with_schedule_to_close_timeout_wrapper_supports_builder_chaining() {
912 let opts = ActivityOptions::with_schedule_to_close_timeout(Duration::from_secs(5))
913 .heartbeat_timeout(Duration::from_secs(2))
914 .build();
915
916 assert_eq!(
917 opts.close_timeouts,
918 ActivityCloseTimeouts::ScheduleToClose(Duration::from_secs(5))
919 );
920 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
921 }
922
923 #[test]
924 fn activity_options_both_close_timeouts_map_to_command() {
925 let req =
926 ActivityOptions::with_close_timeouts(ActivityCloseTimeouts::ScheduleAndStartToClose {
927 start_to_close: Duration::from_secs(3),
928 schedule_to_close: Duration::from_secs(8),
929 })
930 .cancellation_type(ActivityCancellationType::Abandon)
931 .build()
932 .into_command(7, "test".to_string(), vec![], HashMap::new());
933 let Some(workflow_command::Variant::ScheduleActivity(req)) = req.variant else {
934 panic!("expected ScheduleActivity command");
935 };
936 assert_eq!(req.start_to_close_timeout.unwrap().seconds, 3);
937 assert_eq!(req.schedule_to_close_timeout.unwrap().seconds, 8);
938 assert_eq!(
939 req.cancellation_type,
940 ProtoActivityCancellationType::Abandon as i32
941 );
942 }
943
944 #[test]
945 fn local_activity_arguments_marker_option_maps_to_command() {
946 let default_command = LocalActivityOptions::default().into_command(
947 1,
948 "test".to_string(),
949 vec![],
950 HashMap::new(),
951 );
952 let enabled_command = LocalActivityOptions::builder()
953 .include_arguments_in_marker(true)
954 .build()
955 .into_command(1, "test".to_string(), vec![], HashMap::new());
956
957 let Some(workflow_command::Variant::ScheduleLocalActivity(default_command)) =
958 default_command.variant
959 else {
960 panic!("expected ScheduleLocalActivity command");
961 };
962 let Some(workflow_command::Variant::ScheduleLocalActivity(enabled_command)) =
963 enabled_command.variant
964 else {
965 panic!("expected ScheduleLocalActivity command");
966 };
967 assert!(!default_command.include_arguments_in_marker);
968 assert!(enabled_command.include_arguments_in_marker);
969 }
970
971 #[test]
972 fn child_workflow_run_timeout_uses_run_timeout_field() {
973 let opts = ChildWorkflowOptions {
974 workflow_id: Some("test-wf".to_string()),
975 cancel_type: ChildWorkflowCancellationType::WaitCancellationRequested,
976 parent_close_policy: ParentClosePolicy::RequestCancel,
977 id_reuse_policy: WorkflowIdReusePolicy::RejectDuplicate,
978 execution_timeout: Some(Duration::from_secs(60)),
979 run_timeout: Some(Duration::from_secs(10)),
980 ..Default::default()
981 };
982 let command = opts.into_command(
983 1,
984 "TestWorkflow".to_string(),
985 vec![],
986 HashMap::new(),
987 "test-wf".into(),
988 );
989 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
990 else {
991 panic!("expected StartChildWorkflowExecution command");
992 };
993 let exec_timeout = req.workflow_execution_timeout.unwrap();
994 let run_timeout = req.workflow_run_timeout.unwrap();
995 assert_eq!(exec_timeout.seconds, 60);
996 assert_eq!(run_timeout.seconds, 10);
997 assert_eq!(
998 req.cancellation_type,
999 ProtoChildWorkflowCancellationType::WaitCancellationRequested as i32
1000 );
1001 assert_eq!(
1002 req.parent_close_policy,
1003 ProtoParentClosePolicy::RequestCancel as i32
1004 );
1005 assert_eq!(
1006 req.workflow_id_reuse_policy,
1007 ProtoWorkflowIdReusePolicy::RejectDuplicate as i32
1008 );
1009 }
1010
1011 #[test]
1012 fn child_workflow_run_timeout_none_when_unset() {
1013 let opts = ChildWorkflowOptions {
1014 workflow_id: Some("test-wf".to_string()),
1015 execution_timeout: Some(Duration::from_secs(60)),
1016 ..Default::default()
1017 };
1018 let command = opts.into_command(
1019 1,
1020 "TestWorkflow".to_string(),
1021 vec![],
1022 HashMap::new(),
1023 "test-wf".into(),
1024 );
1025 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1026 else {
1027 panic!("expected StartChildWorkflowExecution command");
1028 };
1029 let exec_timeout = req.workflow_execution_timeout.unwrap();
1030 assert_eq!(exec_timeout.seconds, 60);
1031 assert!(req.workflow_run_timeout.is_none());
1032 }
1033}