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 #[doc(hidden)]
293 #[builder(default)]
294 pub event_group_markers: Vec<EventGroupMarker>,
295}
296
297impl ActivityOptions {
298 pub fn with_start_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
300 Self::with_close_timeouts(ActivityCloseTimeouts::StartToClose(duration))
301 }
302
303 pub fn with_schedule_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
305 Self::with_close_timeouts(ActivityCloseTimeouts::ScheduleToClose(duration))
306 }
307
308 pub fn start_to_close_timeout(duration: Duration) -> Self {
312 Self::with_start_to_close_timeout(duration).build()
313 }
314
315 pub fn schedule_to_close_timeout(duration: Duration) -> Self {
319 Self::with_schedule_to_close_timeout(duration).build()
320 }
321}
322
323impl ActivityOptions {
324 pub(crate) fn into_command(
325 self,
326 seq: u32,
327 activity_type: String,
328 args: Vec<Payload>,
329 headers: HashMap<String, Payload>,
330 ) -> WorkflowCommand {
331 #[cfg(feature = "experimental")]
332 let event_group_markers = self.event_group_markers;
333 #[cfg(not(feature = "experimental"))]
334 let event_group_markers = Vec::new();
335 command_with_metadata(
336 workflow_command::Variant::ScheduleActivity(ScheduleActivity {
337 seq,
338 activity_type,
339 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
340 task_queue: self.task_queue.unwrap_or_default(),
341 arguments: args,
342 headers,
343 schedule_to_close_timeout: self
344 .close_timeouts
345 .schedule_to_close()
346 .and_then(|duration| duration.try_into().ok()),
347 schedule_to_start_timeout: self
348 .schedule_to_start_timeout
349 .and_then(|duration| duration.try_into().ok()),
350 start_to_close_timeout: self
351 .close_timeouts
352 .start_to_close()
353 .and_then(|duration| duration.try_into().ok()),
354 heartbeat_timeout: self
355 .heartbeat_timeout
356 .and_then(|duration| duration.try_into().ok()),
357 cancellation_type: ProtoActivityCancellationType::from(self.cancellation_type)
358 .into(),
359 retry_policy: self.retry_policy.map(Into::into),
360 priority: self.priority.map(Into::into),
361 do_not_eagerly_execute: self.do_not_eagerly_execute,
362 ..Default::default()
363 }),
364 self.summary,
365 None,
366 event_group_markers,
367 )
368 }
369}
370
371#[derive(Debug, Clone, bon::Builder)]
373#[non_exhaustive]
374pub struct LocalActivityOptions {
375 pub activity_id: Option<String>,
381 #[builder(default)]
383 pub retry_policy: RetryPolicy,
384 pub attempt: Option<u32>,
387 pub original_schedule_time: Option<prost_types::Timestamp>,
390 pub timer_backoff_threshold: Option<Duration>,
392 #[builder(default)]
394 pub cancel_type: ActivityCancellationType,
395 #[builder(default)]
400 pub include_arguments_in_marker: bool,
401 pub cancellation_token: Option<WorkflowCancellationToken>,
403 pub schedule_to_close_timeout: Option<Duration>,
407 pub schedule_to_start_timeout: Option<Duration>,
413 pub start_to_close_timeout: Option<Duration>,
418 pub summary: Option<String>,
420 #[cfg(feature = "experimental")]
425 #[doc(hidden)]
426 #[builder(default)]
427 pub event_group_markers: Vec<EventGroupMarker>,
428}
429
430impl Default for LocalActivityOptions {
431 fn default() -> Self {
432 Self::builder().build()
433 }
434}
435
436impl LocalActivityOptions {
437 pub(crate) fn into_command(
438 mut self,
439 seq: u32,
440 activity_type: String,
441 args: Vec<Payload>,
442 headers: HashMap<String, Payload>,
443 ) -> WorkflowCommand {
444 self.schedule_to_close_timeout
447 .get_or_insert(Duration::from_secs(100));
448 #[cfg(feature = "experimental")]
449 let event_group_markers = self.event_group_markers;
450 #[cfg(not(feature = "experimental"))]
451 let event_group_markers = Vec::new();
452 command_with_metadata(
453 workflow_command::Variant::ScheduleLocalActivity(ScheduleLocalActivity {
454 seq,
455 activity_type,
456 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
457 arguments: args,
458 headers,
459 retry_policy: Some(self.retry_policy.into()),
460 attempt: self.attempt.unwrap_or(1),
461 original_schedule_time: self.original_schedule_time,
462 local_retry_threshold: self
463 .timer_backoff_threshold
464 .and_then(|duration| duration.try_into().ok()),
465 cancellation_type: ProtoActivityCancellationType::from(self.cancel_type).into(),
466 include_arguments_in_marker: self.include_arguments_in_marker,
467 schedule_to_close_timeout: self
468 .schedule_to_close_timeout
469 .and_then(|duration| duration.try_into().ok()),
470 schedule_to_start_timeout: self
471 .schedule_to_start_timeout
472 .and_then(|duration| duration.try_into().ok()),
473 start_to_close_timeout: self
474 .start_to_close_timeout
475 .and_then(|duration| duration.try_into().ok()),
476 }),
477 self.summary,
478 None,
479 event_group_markers,
480 )
481 }
482}
483
484#[derive(Default, Debug, Clone, bon::Builder)]
486#[non_exhaustive]
487pub struct ChildWorkflowOptions {
488 pub workflow_id: Option<String>,
490 pub task_queue: Option<String>,
494 #[builder(default)]
496 pub cancel_type: ChildWorkflowCancellationType,
497 pub cancellation_token: Option<WorkflowCancellationToken>,
499 #[builder(default)]
501 pub parent_close_policy: ParentClosePolicy,
502 pub static_summary: Option<String>,
504 pub static_details: Option<String>,
506 #[builder(default)]
508 pub id_reuse_policy: WorkflowIdReusePolicy,
509 pub execution_timeout: Option<Duration>,
511 pub run_timeout: Option<Duration>,
513 pub task_timeout: Option<Duration>,
515 pub cron_schedule: Option<String>,
517 pub search_attributes: Option<SearchAttributes>,
519 pub priority: Option<Priority>,
521 #[cfg(feature = "experimental")]
526 #[doc(hidden)]
527 #[builder(default)]
528 pub event_group_markers: Vec<EventGroupMarker>,
529}
530
531impl ChildWorkflowOptions {
532 pub fn workflow_id(workflow_id: String) -> Self {
536 Self::builder().workflow_id(workflow_id).build()
537 }
538
539 pub(crate) fn into_command(
540 self,
541 seq: u32,
542 workflow_type: String,
543 args: Vec<Payload>,
544 headers: HashMap<String, Payload>,
545 workflow_id: String,
546 ) -> WorkflowCommand {
547 #[cfg(feature = "experimental")]
548 let event_group_markers = self.event_group_markers;
549 #[cfg(not(feature = "experimental"))]
550 let event_group_markers = Vec::new();
551 command_with_metadata(
552 workflow_command::Variant::StartChildWorkflowExecution(StartChildWorkflowExecution {
553 seq,
554 workflow_type,
555 workflow_id,
556 task_queue: self.task_queue.unwrap_or_default(),
557 input: args,
558 headers,
559 cancellation_type: ProtoChildWorkflowCancellationType::from(self.cancel_type)
560 .into(),
561 parent_close_policy: ProtoParentClosePolicy::from(self.parent_close_policy).into(),
562 workflow_id_reuse_policy: ProtoWorkflowIdReusePolicy::from(
563 match self.id_reuse_policy {
564 WorkflowIdReusePolicy::Unspecified => WorkflowIdReusePolicy::AllowDuplicate,
565 policy => policy,
566 },
567 )
568 .into(),
569 workflow_execution_timeout: self
570 .execution_timeout
571 .and_then(|duration| duration.try_into().ok()),
572 workflow_run_timeout: self
573 .run_timeout
574 .and_then(|duration| duration.try_into().ok()),
575 workflow_task_timeout: self
576 .task_timeout
577 .and_then(|duration| duration.try_into().ok()),
578 cron_schedule: self.cron_schedule.unwrap_or_default(),
579 search_attributes: self.search_attributes.map(|t| t.into_proto()),
580 priority: self.priority.map(Into::into),
581 ..Default::default()
582 }),
583 self.static_summary,
584 self.static_details,
585 event_group_markers,
586 )
587 }
588}
589
590#[derive(Debug, Clone, bon::Builder)]
592#[non_exhaustive]
593pub struct TimerOptions {
594 #[builder(start_fn)]
596 pub duration: Duration,
597 pub cancellation_token: Option<WorkflowCancellationToken>,
599 pub summary: Option<String>,
601 #[cfg(feature = "experimental")]
606 #[doc(hidden)]
607 #[builder(default)]
608 pub event_group_markers: Vec<EventGroupMarker>,
609}
610
611impl Default for TimerOptions {
612 fn default() -> Self {
613 Self::builder(Duration::default()).build()
614 }
615}
616
617impl From<Duration> for TimerOptions {
618 fn from(duration: Duration) -> Self {
619 TimerOptions {
620 duration,
621 ..Default::default()
622 }
623 }
624}
625
626impl TimerOptions {
627 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
628 #[cfg(feature = "experimental")]
629 let event_group_markers = self.event_group_markers;
630 #[cfg(not(feature = "experimental"))]
631 let event_group_markers = Vec::new();
632 command_with_metadata(
633 workflow_command::Variant::StartTimer(StartTimer {
634 seq,
635 start_to_fire_timeout: Some(
636 self.duration
637 .try_into()
638 .expect("workflow timer timeout must fit into protobuf duration"),
639 ),
640 }),
641 self.summary,
642 None,
643 event_group_markers,
644 )
645 }
646}
647
648#[derive(Default, Debug, Clone, bon::Builder)]
650#[non_exhaustive]
651pub struct WaitConditionOptions {
652 pub cancellation_token: Option<WorkflowCancellationToken>,
654}
655
656#[derive(Default, Debug, Clone, bon::Builder)]
658#[non_exhaustive]
659pub struct SignalWorkflowOptions {
660 pub cancellation_token: Option<WorkflowCancellationToken>,
662 pub summary: Option<String>,
664 #[cfg(feature = "experimental")]
669 #[doc(hidden)]
670 #[builder(default)]
671 pub event_group_markers: Vec<EventGroupMarker>,
672}
673
674impl SignalWorkflowOptions {
675 pub(crate) fn into_command(
676 self,
677 seq: u32,
678 signal_name: String,
679 args: Vec<Payload>,
680 headers: HashMap<String, Payload>,
681 target: signal_external_workflow_execution::Target,
682 ) -> WorkflowCommand {
683 #[cfg(feature = "experimental")]
684 let event_group_markers = self.event_group_markers;
685 #[cfg(not(feature = "experimental"))]
686 let event_group_markers = Vec::new();
687 command_with_metadata(
688 workflow_command::Variant::SignalExternalWorkflowExecution(
689 SignalExternalWorkflowExecution {
690 seq,
691 signal_name,
692 args,
693 target: Some(target),
694 headers,
695 },
696 ),
697 self.summary,
698 None,
699 event_group_markers,
700 )
701 }
702}
703
704#[derive(Default, Debug, bon::Builder)]
708#[non_exhaustive]
709pub struct ContinueAsNewOptions {
710 pub workflow_type: Option<String>,
712 pub task_queue: Option<String>,
714 pub run_timeout: Option<Duration>,
716 pub task_timeout: Option<Duration>,
718 pub backoff_start_interval: Option<Duration>,
720 pub memo: Option<MemoValues>,
722 pub search_attributes: Option<SearchAttributes>,
725 #[builder(into)]
727 pub retry_policy: Option<RetryPolicy>,
728 pub versioning_intent: Option<VersioningIntent>,
730 #[cfg(feature = "experimental")]
736 pub initial_versioning_behavior: Option<ContinueAsNewVersioningBehavior>,
737}
738
739impl ContinueAsNewOptions {
740 pub(crate) fn into_request(
741 self,
742 workflow_type: String,
743 arguments: Vec<Payload>,
744 headers: HashMap<String, Payload>,
745 payload_converter: &PayloadConverter,
746 ) -> Result<ContinueAsNewRequest, PayloadConversionError> {
747 let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
748 let context = SerializationContext::new(&context_data, payload_converter);
749 let memo = self
750 .memo
751 .map(|memo| {
752 memo.iter()
753 .map(|(key, value)| {
754 payload_converter
755 .to_payload(&context, value)
756 .map(|payload| (key.to_owned(), payload))
757 })
758 .collect::<Result<HashMap<_, _>, _>>()
759 })
760 .transpose()?
761 .unwrap_or_default();
762 #[cfg(feature = "experimental")]
763 let initial_versioning_behavior = ProtoContinueAsNewVersioningBehavior::from(
764 self.initial_versioning_behavior
765 .unwrap_or(ContinueAsNewVersioningBehavior::Unspecified),
766 )
767 .into();
768 #[cfg(not(feature = "experimental"))]
769 let initial_versioning_behavior = Default::default();
770 Ok(ContinueAsNewWorkflowExecution {
771 workflow_type: self.workflow_type.unwrap_or(workflow_type),
772 task_queue: self.task_queue.unwrap_or_default(),
773 arguments,
774 workflow_run_timeout: self
775 .run_timeout
776 .and_then(|duration| duration.try_into().ok()),
777 workflow_task_timeout: self
778 .task_timeout
779 .and_then(|duration| duration.try_into().ok()),
780 backoff_start_interval: self
781 .backoff_start_interval
782 .and_then(|duration| duration.try_into().ok()),
783 memo,
784 headers,
785 search_attributes: self.search_attributes.map(|t| t.into_proto()),
786 retry_policy: self.retry_policy.map(Into::into),
787 versioning_intent: ProtoVersioningIntent::from(
788 self.versioning_intent
789 .unwrap_or(VersioningIntent::Unspecified),
790 )
791 .into(),
792 initial_versioning_behavior,
793 })
794 }
795}
796
797fn command_with_metadata(
798 variant: workflow_command::Variant,
799 summary: Option<String>,
800 details: Option<String>,
801 markers: Vec<EventGroupMarker>,
802) -> WorkflowCommand {
803 WorkflowCommand {
804 variant: Some(variant),
805 user_metadata: string_user_metadata(summary, details),
806 event_group_markers: markers,
807 }
808}
809
810fn string_user_metadata(summary: Option<String>, details: Option<String>) -> Option<UserMetadata> {
811 if summary.is_none() && details.is_none() {
812 return None;
813 }
814 let converter = PayloadConverter::default();
815 let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
816 let context = SerializationContext::new(&context_data, &converter);
817 Some(UserMetadata {
818 summary: summary.map(|value| {
819 converter
820 .to_payload(&context, &value)
821 .expect("String-to-JSON payload serialization is infallible")
822 }),
823 details: details.map(|value| {
824 converter
825 .to_payload(&context, &value)
826 .expect("String-to-JSON payload serialization is infallible")
827 }),
828 })
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[test]
836 fn activity_cancellation_default_preserves_sdk_behavior() {
837 assert_eq!(
838 ActivityCancellationType::default(),
839 ActivityCancellationType::TryCancel
840 );
841 }
842
843 #[test]
844 fn child_workflow_cancellation_defaults_to_wait_for_completion() {
845 assert_eq!(
846 ChildWorkflowOptions::default().cancel_type,
847 ChildWorkflowCancellationType::WaitCancellationCompleted
848 );
849 let command = ChildWorkflowOptions::default().into_command(
850 1,
851 "child".to_string(),
852 vec![],
853 HashMap::new(),
854 "child-id".to_string(),
855 );
856 let Some(workflow_command::Variant::StartChildWorkflowExecution(command)) = command.variant
857 else {
858 panic!("expected StartChildWorkflowExecution command");
859 };
860 assert_eq!(
861 command.cancellation_type,
862 ProtoChildWorkflowCancellationType::WaitCancellationCompleted as i32
863 );
864 }
865
866 #[test]
867 fn other_policy_defaults_preserve_sdk_behavior() {
868 assert_eq!(ParentClosePolicy::default(), ParentClosePolicy::Unspecified);
869 assert_eq!(
870 WorkflowIdReusePolicy::default(),
871 WorkflowIdReusePolicy::Unspecified
872 );
873 assert_eq!(VersioningIntent::default(), VersioningIntent::Unspecified);
874 }
875
876 #[test]
877 fn continue_as_new_options_maps_backoff_start_interval_to_request() {
878 let req = ContinueAsNewOptions {
879 backoff_start_interval: Some(Duration::from_secs(7)),
880 versioning_intent: Some(VersioningIntent::Compatible),
881 ..Default::default()
882 }
883 .into_request(
884 "test-workflow".to_string(),
885 vec![],
886 HashMap::new(),
887 &PayloadConverter::default(),
888 )
889 .unwrap();
890
891 let backoff = req
892 .backoff_start_interval
893 .expect("backoff_start_interval should be set");
894 assert_eq!(backoff.seconds, 7);
895 assert_eq!(backoff.nanos, 0);
896 assert_eq!(
897 req.versioning_intent,
898 ProtoVersioningIntent::Compatible as i32
899 );
900 }
901
902 #[test]
903 fn activity_options_with_start_to_close_timeout_wrapper_supports_builder_chaining() {
904 let opts = ActivityOptions::with_start_to_close_timeout(Duration::from_secs(5))
905 .heartbeat_timeout(Duration::from_secs(2))
906 .build();
907
908 assert_eq!(
909 opts.close_timeouts,
910 ActivityCloseTimeouts::StartToClose(Duration::from_secs(5))
911 );
912 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
913 }
914
915 #[test]
916 fn activity_options_with_schedule_to_close_timeout_wrapper_supports_builder_chaining() {
917 let opts = ActivityOptions::with_schedule_to_close_timeout(Duration::from_secs(5))
918 .heartbeat_timeout(Duration::from_secs(2))
919 .build();
920
921 assert_eq!(
922 opts.close_timeouts,
923 ActivityCloseTimeouts::ScheduleToClose(Duration::from_secs(5))
924 );
925 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
926 }
927
928 #[test]
929 fn activity_options_both_close_timeouts_map_to_command() {
930 let req =
931 ActivityOptions::with_close_timeouts(ActivityCloseTimeouts::ScheduleAndStartToClose {
932 start_to_close: Duration::from_secs(3),
933 schedule_to_close: Duration::from_secs(8),
934 })
935 .cancellation_type(ActivityCancellationType::Abandon)
936 .build()
937 .into_command(7, "test".to_string(), vec![], HashMap::new());
938 let Some(workflow_command::Variant::ScheduleActivity(req)) = req.variant else {
939 panic!("expected ScheduleActivity command");
940 };
941 assert_eq!(req.start_to_close_timeout.unwrap().seconds, 3);
942 assert_eq!(req.schedule_to_close_timeout.unwrap().seconds, 8);
943 assert_eq!(
944 req.cancellation_type,
945 ProtoActivityCancellationType::Abandon as i32
946 );
947 }
948
949 #[test]
950 fn local_activity_arguments_marker_option_maps_to_command() {
951 let default_command = LocalActivityOptions::default().into_command(
952 1,
953 "test".to_string(),
954 vec![],
955 HashMap::new(),
956 );
957 let enabled_command = LocalActivityOptions::builder()
958 .include_arguments_in_marker(true)
959 .build()
960 .into_command(1, "test".to_string(), vec![], HashMap::new());
961
962 let Some(workflow_command::Variant::ScheduleLocalActivity(default_command)) =
963 default_command.variant
964 else {
965 panic!("expected ScheduleLocalActivity command");
966 };
967 let Some(workflow_command::Variant::ScheduleLocalActivity(enabled_command)) =
968 enabled_command.variant
969 else {
970 panic!("expected ScheduleLocalActivity command");
971 };
972 assert!(!default_command.include_arguments_in_marker);
973 assert!(enabled_command.include_arguments_in_marker);
974 }
975
976 #[test]
977 fn child_workflow_run_timeout_uses_run_timeout_field() {
978 let opts = ChildWorkflowOptions {
979 workflow_id: Some("test-wf".to_string()),
980 cancel_type: ChildWorkflowCancellationType::WaitCancellationRequested,
981 parent_close_policy: ParentClosePolicy::RequestCancel,
982 id_reuse_policy: WorkflowIdReusePolicy::RejectDuplicate,
983 execution_timeout: Some(Duration::from_secs(60)),
984 run_timeout: Some(Duration::from_secs(10)),
985 ..Default::default()
986 };
987 let command = opts.into_command(
988 1,
989 "TestWorkflow".to_string(),
990 vec![],
991 HashMap::new(),
992 "test-wf".into(),
993 );
994 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
995 else {
996 panic!("expected StartChildWorkflowExecution command");
997 };
998 let exec_timeout = req.workflow_execution_timeout.unwrap();
999 let run_timeout = req.workflow_run_timeout.unwrap();
1000 assert_eq!(exec_timeout.seconds, 60);
1001 assert_eq!(run_timeout.seconds, 10);
1002 assert_eq!(
1003 req.cancellation_type,
1004 ProtoChildWorkflowCancellationType::WaitCancellationRequested as i32
1005 );
1006 assert_eq!(
1007 req.parent_close_policy,
1008 ProtoParentClosePolicy::RequestCancel as i32
1009 );
1010 assert_eq!(
1011 req.workflow_id_reuse_policy,
1012 ProtoWorkflowIdReusePolicy::RejectDuplicate as i32
1013 );
1014 }
1015
1016 #[test]
1017 fn child_workflow_run_timeout_none_when_unset() {
1018 let opts = ChildWorkflowOptions {
1019 workflow_id: Some("test-wf".to_string()),
1020 execution_timeout: Some(Duration::from_secs(60)),
1021 ..Default::default()
1022 };
1023 let command = opts.into_command(
1024 1,
1025 "TestWorkflow".to_string(),
1026 vec![],
1027 HashMap::new(),
1028 "test-wf".into(),
1029 );
1030 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1031 else {
1032 panic!("expected StartChildWorkflowExecution command");
1033 };
1034 let exec_timeout = req.workflow_execution_timeout.unwrap();
1035 assert_eq!(exec_timeout.seconds, 60);
1036 assert!(req.workflow_run_timeout.is_none());
1037 }
1038}