1use std::{collections::HashMap, time::Duration};
2
3use crate::{MemoValues, WorkflowCancellationToken, runtime::types::ContinueAsNewRequest};
4use temporalio_common_wasm::{
5 ActivityCloseTimeouts, Priority, RetryPolicy,
6 data_converters::{
7 GenericPayloadConverter, PayloadConversionError, PayloadConverter, SerializationContext,
8 SerializationContextData,
9 },
10 protos::{
11 coresdk::{
12 child_workflow::{
13 ChildWorkflowCancellationType as ProtoChildWorkflowCancellationType,
14 ParentClosePolicy as ProtoParentClosePolicy,
15 },
16 common::VersioningIntent as ProtoVersioningIntent,
17 nexus::NexusOperationCancellationType as ProtoNexusOperationCancellationType,
18 workflow_commands::{
19 ActivityCancellationType as ProtoActivityCancellationType,
20 ContinueAsNewWorkflowExecution, ScheduleActivity, ScheduleLocalActivity,
21 ScheduleNexusOperation, SignalExternalWorkflowExecution,
22 StartChildWorkflowExecution, StartTimer, WorkflowCommand,
23 signal_external_workflow_execution, workflow_command,
24 },
25 },
26 temporal::api::{
27 common::v1::Payload,
28 enums::v1::{
29 ContinueAsNewVersioningBehavior as ProtoContinueAsNewVersioningBehavior,
30 WorkflowIdReusePolicy as ProtoWorkflowIdReusePolicy,
31 },
32 sdk::v1::{EventGroupMarker, UserMetadata},
33 },
34 },
35 search_attributes::SearchAttributes,
36};
37
38#[derive(
40 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
41)]
42#[non_exhaustive]
43pub enum ActivityCancellationType {
44 #[default]
46 TryCancel,
47 WaitCancellationCompleted,
49 Abandon,
51}
52
53impl From<ActivityCancellationType> for ProtoActivityCancellationType {
54 fn from(value: ActivityCancellationType) -> Self {
55 match value {
56 ActivityCancellationType::TryCancel => Self::TryCancel,
57 ActivityCancellationType::WaitCancellationCompleted => Self::WaitCancellationCompleted,
58 ActivityCancellationType::Abandon => Self::Abandon,
59 }
60 }
61}
62
63impl From<ProtoActivityCancellationType> for ActivityCancellationType {
64 fn from(value: ProtoActivityCancellationType) -> Self {
65 match value {
66 ProtoActivityCancellationType::TryCancel => Self::TryCancel,
67 ProtoActivityCancellationType::WaitCancellationCompleted => {
68 Self::WaitCancellationCompleted
69 }
70 ProtoActivityCancellationType::Abandon => Self::Abandon,
71 }
72 }
73}
74
75#[derive(
77 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
78)]
79#[non_exhaustive]
80pub enum ChildWorkflowCancellationType {
81 Abandon,
83 TryCancel,
85 #[default]
87 WaitCancellationCompleted,
88 WaitCancellationRequested,
90}
91
92impl From<ChildWorkflowCancellationType> for ProtoChildWorkflowCancellationType {
93 fn from(value: ChildWorkflowCancellationType) -> Self {
94 match value {
95 ChildWorkflowCancellationType::Abandon => Self::Abandon,
96 ChildWorkflowCancellationType::TryCancel => Self::TryCancel,
97 ChildWorkflowCancellationType::WaitCancellationCompleted => {
98 Self::WaitCancellationCompleted
99 }
100 ChildWorkflowCancellationType::WaitCancellationRequested => {
101 Self::WaitCancellationRequested
102 }
103 }
104 }
105}
106
107impl From<ProtoChildWorkflowCancellationType> for ChildWorkflowCancellationType {
108 fn from(value: ProtoChildWorkflowCancellationType) -> Self {
109 match value {
110 ProtoChildWorkflowCancellationType::Abandon => Self::Abandon,
111 ProtoChildWorkflowCancellationType::TryCancel => Self::TryCancel,
112 ProtoChildWorkflowCancellationType::WaitCancellationCompleted => {
113 Self::WaitCancellationCompleted
114 }
115 ProtoChildWorkflowCancellationType::WaitCancellationRequested => {
116 Self::WaitCancellationRequested
117 }
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
124#[non_exhaustive]
125pub enum ParentClosePolicy {
126 #[default]
128 Unspecified,
129 Terminate,
131 Abandon,
133 RequestCancel,
135}
136
137impl From<ParentClosePolicy> for ProtoParentClosePolicy {
138 fn from(value: ParentClosePolicy) -> Self {
139 match value {
140 ParentClosePolicy::Unspecified => Self::Unspecified,
141 ParentClosePolicy::Terminate => Self::Terminate,
142 ParentClosePolicy::Abandon => Self::Abandon,
143 ParentClosePolicy::RequestCancel => Self::RequestCancel,
144 }
145 }
146}
147
148impl From<ProtoParentClosePolicy> for ParentClosePolicy {
149 fn from(value: ProtoParentClosePolicy) -> Self {
150 match value {
151 ProtoParentClosePolicy::Unspecified => Self::Unspecified,
152 ProtoParentClosePolicy::Terminate => Self::Terminate,
153 ProtoParentClosePolicy::Abandon => Self::Abandon,
154 ProtoParentClosePolicy::RequestCancel => Self::RequestCancel,
155 }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
161#[non_exhaustive]
162pub enum WorkflowIdReusePolicy {
163 #[default]
165 Unspecified,
166 AllowDuplicate,
168 AllowDuplicateFailedOnly,
170 RejectDuplicate,
172 TerminateIfRunning,
174}
175
176impl From<WorkflowIdReusePolicy> for ProtoWorkflowIdReusePolicy {
177 #[allow(deprecated)]
178 fn from(value: WorkflowIdReusePolicy) -> Self {
179 match value {
180 WorkflowIdReusePolicy::Unspecified => Self::Unspecified,
181 WorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
182 WorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
183 WorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
184 WorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
185 }
186 }
187}
188
189impl From<ProtoWorkflowIdReusePolicy> for WorkflowIdReusePolicy {
190 #[allow(deprecated)]
191 fn from(value: ProtoWorkflowIdReusePolicy) -> Self {
192 match value {
193 ProtoWorkflowIdReusePolicy::Unspecified => Self::Unspecified,
194 ProtoWorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
195 ProtoWorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
196 ProtoWorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
197 ProtoWorkflowIdReusePolicy::TerminateIfRunning => Self::TerminateIfRunning,
198 }
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
204#[non_exhaustive]
205pub enum VersioningIntent {
206 #[default]
208 Unspecified,
209 Compatible,
211 Default,
213}
214
215impl From<VersioningIntent> for ProtoVersioningIntent {
216 fn from(value: VersioningIntent) -> Self {
217 match value {
218 VersioningIntent::Unspecified => Self::Unspecified,
219 VersioningIntent::Compatible => Self::Compatible,
220 VersioningIntent::Default => Self::Default,
221 }
222 }
223}
224
225impl From<ProtoVersioningIntent> for VersioningIntent {
226 fn from(value: ProtoVersioningIntent) -> Self {
227 match value {
228 ProtoVersioningIntent::Unspecified => Self::Unspecified,
229 ProtoVersioningIntent::Compatible => Self::Compatible,
230 ProtoVersioningIntent::Default => Self::Default,
231 }
232 }
233}
234
235#[derive(
237 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
238)]
239#[non_exhaustive]
240pub enum NexusOperationCancellationType {
241 #[default]
243 WaitCancellationCompleted,
244 Abandon,
246 TryCancel,
248 WaitCancellationRequested,
250}
251
252impl From<NexusOperationCancellationType> for ProtoNexusOperationCancellationType {
253 fn from(value: NexusOperationCancellationType) -> Self {
254 match value {
255 NexusOperationCancellationType::WaitCancellationCompleted => {
256 Self::WaitCancellationCompleted
257 }
258 NexusOperationCancellationType::Abandon => Self::Abandon,
259 NexusOperationCancellationType::TryCancel => Self::TryCancel,
260 NexusOperationCancellationType::WaitCancellationRequested => {
261 Self::WaitCancellationRequested
262 }
263 }
264 }
265}
266
267impl From<ProtoNexusOperationCancellationType> for NexusOperationCancellationType {
268 fn from(value: ProtoNexusOperationCancellationType) -> Self {
269 match value {
270 ProtoNexusOperationCancellationType::WaitCancellationCompleted => {
271 Self::WaitCancellationCompleted
272 }
273 ProtoNexusOperationCancellationType::Abandon => Self::Abandon,
274 ProtoNexusOperationCancellationType::TryCancel => Self::TryCancel,
275 ProtoNexusOperationCancellationType::WaitCancellationRequested => {
276 Self::WaitCancellationRequested
277 }
278 }
279 }
280}
281#[derive(Debug, bon::Builder, Clone)]
283#[non_exhaustive]
284#[builder(start_fn = with_close_timeouts, on(String, into), state_mod(vis = "pub"))]
285pub struct ActivityOptions {
286 #[builder(start_fn)]
290 pub close_timeouts: ActivityCloseTimeouts,
291 pub activity_id: Option<String>,
297 pub task_queue: Option<String>,
301 pub schedule_to_start_timeout: Option<Duration>,
308 pub heartbeat_timeout: Option<Duration>,
311 #[builder(default, into)]
313 pub cancellation_type: ActivityCancellationType,
314 pub cancellation_token: Option<WorkflowCancellationToken>,
316 #[builder(into)]
318 pub retry_policy: Option<RetryPolicy>,
319 pub summary: Option<String>,
321 pub priority: Option<Priority>,
323 #[builder(default)]
325 pub do_not_eagerly_execute: bool,
326 #[doc(hidden)]
331 #[builder(default)]
332 pub event_group_markers: Vec<EventGroupMarker>,
333}
334
335impl ActivityOptions {
336 pub fn with_start_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
338 Self::with_close_timeouts(ActivityCloseTimeouts::StartToClose(duration))
339 }
340
341 pub fn with_schedule_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
343 Self::with_close_timeouts(ActivityCloseTimeouts::ScheduleToClose(duration))
344 }
345
346 pub fn start_to_close_timeout(duration: Duration) -> Self {
350 Self::with_start_to_close_timeout(duration).build()
351 }
352
353 pub fn schedule_to_close_timeout(duration: Duration) -> Self {
357 Self::with_schedule_to_close_timeout(duration).build()
358 }
359}
360
361impl ActivityOptions {
362 pub(crate) fn into_command(
363 self,
364 seq: u32,
365 activity_type: String,
366 args: Vec<Payload>,
367 headers: HashMap<String, Payload>,
368 ) -> WorkflowCommand {
369 command_with_metadata(
370 workflow_command::Variant::ScheduleActivity(ScheduleActivity {
371 seq,
372 activity_type,
373 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
374 task_queue: self.task_queue.unwrap_or_default(),
375 arguments: args,
376 headers,
377 schedule_to_close_timeout: self
378 .close_timeouts
379 .schedule_to_close()
380 .and_then(|duration| duration.try_into().ok()),
381 schedule_to_start_timeout: self
382 .schedule_to_start_timeout
383 .and_then(|duration| duration.try_into().ok()),
384 start_to_close_timeout: self
385 .close_timeouts
386 .start_to_close()
387 .and_then(|duration| duration.try_into().ok()),
388 heartbeat_timeout: self
389 .heartbeat_timeout
390 .and_then(|duration| duration.try_into().ok()),
391 cancellation_type: ProtoActivityCancellationType::from(self.cancellation_type)
392 .into(),
393 retry_policy: self.retry_policy.map(Into::into),
394 priority: self.priority.map(Into::into),
395 do_not_eagerly_execute: self.do_not_eagerly_execute,
396 ..Default::default()
397 }),
398 self.summary,
399 None,
400 self.event_group_markers,
401 )
402 }
403}
404
405#[derive(Debug, Clone, bon::Builder)]
407#[non_exhaustive]
408pub struct LocalActivityOptions {
409 pub activity_id: Option<String>,
415 #[builder(default)]
417 pub retry_policy: RetryPolicy,
418 pub attempt: Option<u32>,
421 pub original_schedule_time: Option<prost_types::Timestamp>,
424 pub timer_backoff_threshold: Option<Duration>,
426 #[builder(default)]
428 pub cancel_type: ActivityCancellationType,
429 pub cancellation_token: Option<WorkflowCancellationToken>,
431 pub schedule_to_close_timeout: Option<Duration>,
435 pub schedule_to_start_timeout: Option<Duration>,
441 pub start_to_close_timeout: Option<Duration>,
446 pub summary: Option<String>,
448 #[doc(hidden)]
453 #[builder(default)]
454 pub event_group_markers: Vec<EventGroupMarker>,
455}
456
457impl Default for LocalActivityOptions {
458 fn default() -> Self {
459 Self::builder().build()
460 }
461}
462
463impl LocalActivityOptions {
464 pub(crate) fn into_command(
465 mut self,
466 seq: u32,
467 activity_type: String,
468 args: Vec<Payload>,
469 headers: HashMap<String, Payload>,
470 ) -> WorkflowCommand {
471 self.schedule_to_close_timeout
474 .get_or_insert(Duration::from_secs(100));
475 command_with_metadata(
476 workflow_command::Variant::ScheduleLocalActivity(ScheduleLocalActivity {
477 seq,
478 activity_type,
479 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
480 arguments: args,
481 headers,
482 retry_policy: Some(self.retry_policy.into()),
483 attempt: self.attempt.unwrap_or(1),
484 original_schedule_time: self.original_schedule_time,
485 local_retry_threshold: self
486 .timer_backoff_threshold
487 .and_then(|duration| duration.try_into().ok()),
488 cancellation_type: ProtoActivityCancellationType::from(self.cancel_type).into(),
489 schedule_to_close_timeout: self
490 .schedule_to_close_timeout
491 .and_then(|duration| duration.try_into().ok()),
492 schedule_to_start_timeout: self
493 .schedule_to_start_timeout
494 .and_then(|duration| duration.try_into().ok()),
495 start_to_close_timeout: self
496 .start_to_close_timeout
497 .and_then(|duration| duration.try_into().ok()),
498 }),
499 self.summary,
500 None,
501 self.event_group_markers,
502 )
503 }
504}
505
506#[derive(Default, Debug, Clone, bon::Builder)]
508#[non_exhaustive]
509pub struct ChildWorkflowOptions {
510 pub workflow_id: Option<String>,
512 pub task_queue: Option<String>,
516 #[builder(default)]
518 pub cancel_type: ChildWorkflowCancellationType,
519 pub cancellation_token: Option<WorkflowCancellationToken>,
521 #[builder(default)]
523 pub parent_close_policy: ParentClosePolicy,
524 pub static_summary: Option<String>,
526 pub static_details: Option<String>,
528 #[builder(default)]
530 pub id_reuse_policy: WorkflowIdReusePolicy,
531 pub execution_timeout: Option<Duration>,
533 pub run_timeout: Option<Duration>,
535 pub task_timeout: Option<Duration>,
537 pub cron_schedule: Option<String>,
539 pub search_attributes: Option<SearchAttributes>,
541 pub priority: Option<Priority>,
543 #[doc(hidden)]
548 #[builder(default)]
549 pub event_group_markers: Vec<EventGroupMarker>,
550}
551
552impl ChildWorkflowOptions {
553 pub fn workflow_id(workflow_id: String) -> Self {
557 Self::builder().workflow_id(workflow_id).build()
558 }
559
560 pub(crate) fn into_command(
561 self,
562 seq: u32,
563 workflow_type: String,
564 args: Vec<Payload>,
565 headers: HashMap<String, Payload>,
566 workflow_id: String,
567 ) -> WorkflowCommand {
568 command_with_metadata(
569 workflow_command::Variant::StartChildWorkflowExecution(StartChildWorkflowExecution {
570 seq,
571 workflow_type,
572 workflow_id,
573 task_queue: self.task_queue.unwrap_or_default(),
574 input: args,
575 headers,
576 cancellation_type: ProtoChildWorkflowCancellationType::from(self.cancel_type)
577 .into(),
578 parent_close_policy: ProtoParentClosePolicy::from(self.parent_close_policy).into(),
579 workflow_id_reuse_policy: ProtoWorkflowIdReusePolicy::from(
580 match self.id_reuse_policy {
581 WorkflowIdReusePolicy::Unspecified => WorkflowIdReusePolicy::AllowDuplicate,
582 policy => policy,
583 },
584 )
585 .into(),
586 workflow_execution_timeout: self
587 .execution_timeout
588 .and_then(|duration| duration.try_into().ok()),
589 workflow_run_timeout: self
590 .run_timeout
591 .and_then(|duration| duration.try_into().ok()),
592 workflow_task_timeout: self
593 .task_timeout
594 .and_then(|duration| duration.try_into().ok()),
595 cron_schedule: self.cron_schedule.unwrap_or_default(),
596 search_attributes: self.search_attributes.map(|t| t.into_proto()),
597 priority: self.priority.map(Into::into),
598 ..Default::default()
599 }),
600 self.static_summary,
601 self.static_details,
602 self.event_group_markers,
603 )
604 }
605}
606
607#[derive(Debug, Clone, bon::Builder)]
609#[non_exhaustive]
610pub struct TimerOptions {
611 #[builder(start_fn)]
613 pub duration: Duration,
614 pub cancellation_token: Option<WorkflowCancellationToken>,
616 pub summary: Option<String>,
618 #[doc(hidden)]
623 #[builder(default)]
624 pub event_group_markers: Vec<EventGroupMarker>,
625}
626
627impl Default for TimerOptions {
628 fn default() -> Self {
629 Self::builder(Duration::default()).build()
630 }
631}
632
633impl From<Duration> for TimerOptions {
634 fn from(duration: Duration) -> Self {
635 TimerOptions {
636 duration,
637 ..Default::default()
638 }
639 }
640}
641
642impl TimerOptions {
643 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
644 command_with_metadata(
645 workflow_command::Variant::StartTimer(StartTimer {
646 seq,
647 start_to_fire_timeout: Some(
648 self.duration
649 .try_into()
650 .expect("workflow timer timeout must fit into protobuf duration"),
651 ),
652 }),
653 self.summary,
654 None,
655 self.event_group_markers,
656 )
657 }
658}
659
660#[derive(Default, Debug, Clone, bon::Builder)]
662#[non_exhaustive]
663pub struct WaitConditionOptions {
664 pub cancellation_token: Option<WorkflowCancellationToken>,
666}
667
668#[derive(Default, Debug, Clone, bon::Builder)]
670#[non_exhaustive]
671pub struct SignalWorkflowOptions {
672 pub cancellation_token: Option<WorkflowCancellationToken>,
674 pub summary: Option<String>,
676 #[doc(hidden)]
681 #[builder(default)]
682 pub event_group_markers: Vec<EventGroupMarker>,
683}
684
685impl SignalWorkflowOptions {
686 pub(crate) fn into_command(
687 self,
688 seq: u32,
689 signal_name: String,
690 args: Vec<Payload>,
691 headers: HashMap<String, Payload>,
692 target: signal_external_workflow_execution::Target,
693 ) -> WorkflowCommand {
694 command_with_metadata(
695 workflow_command::Variant::SignalExternalWorkflowExecution(
696 SignalExternalWorkflowExecution {
697 seq,
698 signal_name,
699 args,
700 target: Some(target),
701 headers,
702 },
703 ),
704 self.summary,
705 None,
706 self.event_group_markers,
707 )
708 }
709}
710
711#[derive(Debug, Clone, bon::Builder)]
713#[builder(on(String, into))]
714#[non_exhaustive]
715pub struct NexusOperationOptions {
716 pub endpoint: String,
718 pub service: String,
720 pub operation: String,
722 pub input: Option<Payload>,
727 pub schedule_to_close_timeout: Option<Duration>,
731 #[builder(default)]
738 pub nexus_header: HashMap<String, String>,
739 pub cancellation_type: Option<NexusOperationCancellationType>,
741 pub cancellation_token: Option<WorkflowCancellationToken>,
743 pub schedule_to_start_timeout: Option<Duration>,
749 pub start_to_close_timeout: Option<Duration>,
756}
757
758impl NexusOperationOptions {
759 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
760 workflow_command::Variant::ScheduleNexusOperation(ScheduleNexusOperation {
761 seq,
762 endpoint: self.endpoint,
763 service: self.service,
764 operation: self.operation,
765 input: self.input,
766 schedule_to_close_timeout: self
767 .schedule_to_close_timeout
768 .and_then(|duration| duration.try_into().ok()),
769 schedule_to_start_timeout: self
770 .schedule_to_start_timeout
771 .and_then(|duration| duration.try_into().ok()),
772 start_to_close_timeout: self
773 .start_to_close_timeout
774 .and_then(|duration| duration.try_into().ok()),
775 nexus_header: self.nexus_header,
776 cancellation_type: ProtoNexusOperationCancellationType::from(
777 self.cancellation_type
778 .unwrap_or(NexusOperationCancellationType::WaitCancellationCompleted),
779 )
780 .into(),
781 })
782 .into()
783 }
784}
785
786#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
788#[non_exhaustive]
789pub enum ContinueAsNewVersioningBehavior {
790 #[default]
792 Unspecified,
793 AutoUpgrade,
795 UseRampingVersion,
797}
798
799impl From<ContinueAsNewVersioningBehavior> for ProtoContinueAsNewVersioningBehavior {
800 fn from(value: ContinueAsNewVersioningBehavior) -> Self {
801 match value {
802 ContinueAsNewVersioningBehavior::Unspecified => {
803 ProtoContinueAsNewVersioningBehavior::Unspecified
804 }
805 ContinueAsNewVersioningBehavior::AutoUpgrade => {
806 ProtoContinueAsNewVersioningBehavior::AutoUpgrade
807 }
808 ContinueAsNewVersioningBehavior::UseRampingVersion => {
809 ProtoContinueAsNewVersioningBehavior::UseRampingVersion
810 }
811 }
812 }
813}
814
815impl From<ProtoContinueAsNewVersioningBehavior> for ContinueAsNewVersioningBehavior {
816 fn from(value: ProtoContinueAsNewVersioningBehavior) -> Self {
817 match value {
818 ProtoContinueAsNewVersioningBehavior::Unspecified => {
819 ContinueAsNewVersioningBehavior::Unspecified
820 }
821 ProtoContinueAsNewVersioningBehavior::AutoUpgrade => {
822 ContinueAsNewVersioningBehavior::AutoUpgrade
823 }
824 ProtoContinueAsNewVersioningBehavior::UseRampingVersion => {
825 ContinueAsNewVersioningBehavior::UseRampingVersion
826 }
827 }
828 }
829}
830
831#[derive(Default, Debug, bon::Builder)]
835#[non_exhaustive]
836pub struct ContinueAsNewOptions {
837 pub workflow_type: Option<String>,
839 pub task_queue: Option<String>,
841 pub run_timeout: Option<Duration>,
843 pub task_timeout: Option<Duration>,
845 pub backoff_start_interval: Option<Duration>,
847 pub memo: Option<MemoValues>,
849 pub search_attributes: Option<SearchAttributes>,
852 #[builder(into)]
854 pub retry_policy: Option<RetryPolicy>,
855 pub versioning_intent: Option<VersioningIntent>,
857 pub initial_versioning_behavior: Option<ContinueAsNewVersioningBehavior>,
863}
864
865impl ContinueAsNewOptions {
866 pub(crate) fn into_request(
867 self,
868 workflow_type: String,
869 arguments: Vec<Payload>,
870 headers: HashMap<String, Payload>,
871 payload_converter: &PayloadConverter,
872 ) -> Result<ContinueAsNewRequest, PayloadConversionError> {
873 let memo = self
874 .memo
875 .map(|memo| memo.encode(payload_converter))
876 .transpose()?
877 .unwrap_or_default();
878 Ok(ContinueAsNewWorkflowExecution {
879 workflow_type: self.workflow_type.unwrap_or(workflow_type),
880 task_queue: self.task_queue.unwrap_or_default(),
881 arguments,
882 workflow_run_timeout: self
883 .run_timeout
884 .and_then(|duration| duration.try_into().ok()),
885 workflow_task_timeout: self
886 .task_timeout
887 .and_then(|duration| duration.try_into().ok()),
888 backoff_start_interval: self
889 .backoff_start_interval
890 .and_then(|duration| duration.try_into().ok()),
891 memo,
892 headers,
893 search_attributes: self.search_attributes.map(|t| t.into_proto()),
894 retry_policy: self.retry_policy.map(Into::into),
895 versioning_intent: ProtoVersioningIntent::from(
896 self.versioning_intent
897 .unwrap_or(VersioningIntent::Unspecified),
898 )
899 .into(),
900 initial_versioning_behavior: ProtoContinueAsNewVersioningBehavior::from(
901 self.initial_versioning_behavior
902 .unwrap_or(ContinueAsNewVersioningBehavior::Unspecified),
903 )
904 .into(),
905 })
906 }
907}
908
909fn command_with_metadata(
910 variant: workflow_command::Variant,
911 summary: Option<String>,
912 details: Option<String>,
913 markers: Vec<EventGroupMarker>,
914) -> WorkflowCommand {
915 WorkflowCommand {
916 variant: Some(variant),
917 user_metadata: string_user_metadata(summary, details),
918 event_group_markers: markers,
919 }
920}
921
922fn string_user_metadata(summary: Option<String>, details: Option<String>) -> Option<UserMetadata> {
923 if summary.is_none() && details.is_none() {
924 return None;
925 }
926 let converter = PayloadConverter::default();
927 let context = SerializationContext {
928 data: &SerializationContextData::Workflow,
929 converter: &converter,
930 };
931 Some(UserMetadata {
932 summary: summary.map(|value| {
933 converter
934 .to_payload(&context, &value)
935 .expect("String-to-JSON payload serialization is infallible")
936 }),
937 details: details.map(|value| {
938 converter
939 .to_payload(&context, &value)
940 .expect("String-to-JSON payload serialization is infallible")
941 }),
942 })
943}
944
945#[cfg(test)]
946mod tests {
947 use super::*;
948
949 #[test]
950 fn activity_cancellation_default_preserves_sdk_behavior() {
951 assert_eq!(
952 ActivityCancellationType::default(),
953 ActivityCancellationType::TryCancel
954 );
955 }
956
957 #[test]
958 fn child_workflow_cancellation_defaults_to_wait_for_completion() {
959 assert_eq!(
960 ChildWorkflowOptions::default().cancel_type,
961 ChildWorkflowCancellationType::WaitCancellationCompleted
962 );
963 let command = ChildWorkflowOptions::default().into_command(
964 1,
965 "child".to_string(),
966 vec![],
967 HashMap::new(),
968 "child-id".to_string(),
969 );
970 let Some(workflow_command::Variant::StartChildWorkflowExecution(command)) = command.variant
971 else {
972 panic!("expected StartChildWorkflowExecution command");
973 };
974 assert_eq!(
975 command.cancellation_type,
976 ProtoChildWorkflowCancellationType::WaitCancellationCompleted as i32
977 );
978 }
979
980 #[test]
981 fn other_policy_defaults_preserve_sdk_behavior() {
982 assert_eq!(ParentClosePolicy::default(), ParentClosePolicy::Unspecified);
983 assert_eq!(
984 WorkflowIdReusePolicy::default(),
985 WorkflowIdReusePolicy::Unspecified
986 );
987 assert_eq!(VersioningIntent::default(), VersioningIntent::Unspecified);
988 assert_eq!(
989 NexusOperationCancellationType::default(),
990 NexusOperationCancellationType::WaitCancellationCompleted
991 );
992 }
993
994 #[test]
995 fn continue_as_new_options_maps_backoff_start_interval_to_request() {
996 let req = ContinueAsNewOptions {
997 backoff_start_interval: Some(Duration::from_secs(7)),
998 versioning_intent: Some(VersioningIntent::Compatible),
999 ..Default::default()
1000 }
1001 .into_request(
1002 "test-workflow".to_string(),
1003 vec![],
1004 HashMap::new(),
1005 &PayloadConverter::default(),
1006 )
1007 .unwrap();
1008
1009 let backoff = req
1010 .backoff_start_interval
1011 .expect("backoff_start_interval should be set");
1012 assert_eq!(backoff.seconds, 7);
1013 assert_eq!(backoff.nanos, 0);
1014 assert_eq!(
1015 req.versioning_intent,
1016 ProtoVersioningIntent::Compatible as i32
1017 );
1018 }
1019
1020 #[test]
1021 fn activity_options_with_start_to_close_timeout_wrapper_supports_builder_chaining() {
1022 let opts = ActivityOptions::with_start_to_close_timeout(Duration::from_secs(5))
1023 .heartbeat_timeout(Duration::from_secs(2))
1024 .build();
1025
1026 assert_eq!(
1027 opts.close_timeouts,
1028 ActivityCloseTimeouts::StartToClose(Duration::from_secs(5))
1029 );
1030 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
1031 }
1032
1033 #[test]
1034 fn activity_options_with_schedule_to_close_timeout_wrapper_supports_builder_chaining() {
1035 let opts = ActivityOptions::with_schedule_to_close_timeout(Duration::from_secs(5))
1036 .heartbeat_timeout(Duration::from_secs(2))
1037 .build();
1038
1039 assert_eq!(
1040 opts.close_timeouts,
1041 ActivityCloseTimeouts::ScheduleToClose(Duration::from_secs(5))
1042 );
1043 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
1044 }
1045
1046 #[test]
1047 fn activity_options_both_close_timeouts_map_to_command() {
1048 let req = ActivityOptions::with_close_timeouts(ActivityCloseTimeouts::Both {
1049 start_to_close: Duration::from_secs(3),
1050 schedule_to_close: Duration::from_secs(8),
1051 })
1052 .cancellation_type(ActivityCancellationType::Abandon)
1053 .build()
1054 .into_command(7, "test".to_string(), vec![], HashMap::new());
1055 let Some(workflow_command::Variant::ScheduleActivity(req)) = req.variant else {
1056 panic!("expected ScheduleActivity command");
1057 };
1058 assert_eq!(req.start_to_close_timeout.unwrap().seconds, 3);
1059 assert_eq!(req.schedule_to_close_timeout.unwrap().seconds, 8);
1060 assert_eq!(
1061 req.cancellation_type,
1062 ProtoActivityCancellationType::Abandon as i32
1063 );
1064 }
1065
1066 #[test]
1067 fn child_workflow_run_timeout_uses_run_timeout_field() {
1068 let opts = ChildWorkflowOptions {
1069 workflow_id: Some("test-wf".to_string()),
1070 cancel_type: ChildWorkflowCancellationType::WaitCancellationRequested,
1071 parent_close_policy: ParentClosePolicy::RequestCancel,
1072 id_reuse_policy: WorkflowIdReusePolicy::RejectDuplicate,
1073 execution_timeout: Some(Duration::from_secs(60)),
1074 run_timeout: Some(Duration::from_secs(10)),
1075 ..Default::default()
1076 };
1077 let command = opts.into_command(
1078 1,
1079 "TestWorkflow".to_string(),
1080 vec![],
1081 HashMap::new(),
1082 "test-wf".into(),
1083 );
1084 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1085 else {
1086 panic!("expected StartChildWorkflowExecution command");
1087 };
1088 let exec_timeout = req.workflow_execution_timeout.unwrap();
1089 let run_timeout = req.workflow_run_timeout.unwrap();
1090 assert_eq!(exec_timeout.seconds, 60);
1091 assert_eq!(run_timeout.seconds, 10);
1092 assert_eq!(
1093 req.cancellation_type,
1094 ProtoChildWorkflowCancellationType::WaitCancellationRequested as i32
1095 );
1096 assert_eq!(
1097 req.parent_close_policy,
1098 ProtoParentClosePolicy::RequestCancel as i32
1099 );
1100 assert_eq!(
1101 req.workflow_id_reuse_policy,
1102 ProtoWorkflowIdReusePolicy::RejectDuplicate as i32
1103 );
1104 }
1105
1106 #[test]
1107 fn child_workflow_run_timeout_none_when_unset() {
1108 let opts = ChildWorkflowOptions {
1109 workflow_id: Some("test-wf".to_string()),
1110 execution_timeout: Some(Duration::from_secs(60)),
1111 ..Default::default()
1112 };
1113 let command = opts.into_command(
1114 1,
1115 "TestWorkflow".to_string(),
1116 vec![],
1117 HashMap::new(),
1118 "test-wf".into(),
1119 );
1120 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1121 else {
1122 panic!("expected StartChildWorkflowExecution command");
1123 };
1124 let exec_timeout = req.workflow_execution_timeout.unwrap();
1125 assert_eq!(exec_timeout.seconds, 60);
1126 assert!(req.workflow_run_timeout.is_none());
1127 }
1128}