1use std::{collections::HashMap, time::Duration};
2
3use crate::{MemoValues, runtime::types::ContinueAsNewRequest};
4use temporalio_common_wasm::{
5 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_activation::SignalWorkflow,
19 workflow_commands::{
20 ActivityCancellationType as ProtoActivityCancellationType,
21 ContinueAsNewWorkflowExecution, ScheduleActivity, ScheduleLocalActivity,
22 ScheduleNexusOperation, StartChildWorkflowExecution, StartTimer, WorkflowCommand,
23 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::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 #[builder(into)]
316 pub retry_policy: Option<RetryPolicy>,
317 pub summary: Option<String>,
319 pub priority: Option<Priority>,
321 #[builder(default)]
323 pub do_not_eagerly_execute: bool,
324}
325
326impl ActivityOptions {
327 pub fn with_start_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
329 Self::with_close_timeouts(ActivityCloseTimeouts::StartToClose(duration))
330 }
331
332 pub fn with_schedule_to_close_timeout(duration: Duration) -> ActivityOptionsBuilder {
334 Self::with_close_timeouts(ActivityCloseTimeouts::ScheduleToClose(duration))
335 }
336
337 pub fn start_to_close_timeout(duration: Duration) -> Self {
341 Self::with_start_to_close_timeout(duration).build()
342 }
343
344 pub fn schedule_to_close_timeout(duration: Duration) -> Self {
348 Self::with_schedule_to_close_timeout(duration).build()
349 }
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum ActivityCloseTimeouts {
355 ScheduleToClose(Duration),
360 StartToClose(Duration),
367 Both {
369 start_to_close: Duration,
371 schedule_to_close: Duration,
373 },
374}
375
376impl ActivityCloseTimeouts {
377 fn into_durations(self) -> (Option<Duration>, Option<Duration>) {
378 match self {
379 Self::ScheduleToClose(schedule_to_close) => (None, Some(schedule_to_close)),
380 Self::StartToClose(start_to_close) => (Some(start_to_close), None),
381 Self::Both {
382 start_to_close,
383 schedule_to_close,
384 } => (Some(start_to_close), Some(schedule_to_close)),
385 }
386 }
387}
388
389impl ActivityOptions {
390 pub(crate) fn into_command(
391 self,
392 seq: u32,
393 activity_type: String,
394 args: Vec<Payload>,
395 headers: HashMap<String, Payload>,
396 ) -> WorkflowCommand {
397 let (start_to_close_timeout, schedule_to_close_timeout) =
398 self.close_timeouts.into_durations();
399 command_with_metadata(
400 workflow_command::Variant::ScheduleActivity(ScheduleActivity {
401 seq,
402 activity_type,
403 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
404 task_queue: self.task_queue.unwrap_or_default(),
405 arguments: args,
406 headers,
407 schedule_to_close_timeout: schedule_to_close_timeout
408 .and_then(|duration| duration.try_into().ok()),
409 schedule_to_start_timeout: self
410 .schedule_to_start_timeout
411 .and_then(|duration| duration.try_into().ok()),
412 start_to_close_timeout: start_to_close_timeout
413 .and_then(|duration| duration.try_into().ok()),
414 heartbeat_timeout: self
415 .heartbeat_timeout
416 .and_then(|duration| duration.try_into().ok()),
417 cancellation_type: ProtoActivityCancellationType::from(self.cancellation_type)
418 .into(),
419 retry_policy: self.retry_policy.map(Into::into),
420 priority: self.priority.map(Into::into),
421 do_not_eagerly_execute: self.do_not_eagerly_execute,
422 ..Default::default()
423 }),
424 self.summary,
425 None,
426 )
427 }
428}
429
430#[derive(Default, Debug, Clone)]
432pub struct LocalActivityOptions {
433 pub activity_id: Option<String>,
439 pub retry_policy: RetryPolicy,
441 pub attempt: Option<u32>,
444 pub original_schedule_time: Option<prost_types::Timestamp>,
447 pub timer_backoff_threshold: Option<Duration>,
449 pub cancel_type: ActivityCancellationType,
451 pub schedule_to_close_timeout: Option<Duration>,
455 pub schedule_to_start_timeout: Option<Duration>,
461 pub start_to_close_timeout: Option<Duration>,
466 pub summary: Option<String>,
468}
469
470impl LocalActivityOptions {
471 pub(crate) fn into_command(
472 mut self,
473 seq: u32,
474 activity_type: String,
475 args: Vec<Payload>,
476 headers: HashMap<String, Payload>,
477 ) -> WorkflowCommand {
478 self.schedule_to_close_timeout
481 .get_or_insert(Duration::from_secs(100));
482 command_with_metadata(
483 workflow_command::Variant::ScheduleLocalActivity(ScheduleLocalActivity {
484 seq,
485 activity_type,
486 activity_id: self.activity_id.unwrap_or_else(|| seq.to_string()),
487 arguments: args,
488 headers,
489 retry_policy: Some(self.retry_policy.into()),
490 attempt: self.attempt.unwrap_or(1),
491 original_schedule_time: self.original_schedule_time,
492 local_retry_threshold: self
493 .timer_backoff_threshold
494 .and_then(|duration| duration.try_into().ok()),
495 cancellation_type: ProtoActivityCancellationType::from(self.cancel_type).into(),
496 schedule_to_close_timeout: self
497 .schedule_to_close_timeout
498 .and_then(|duration| duration.try_into().ok()),
499 schedule_to_start_timeout: self
500 .schedule_to_start_timeout
501 .and_then(|duration| duration.try_into().ok()),
502 start_to_close_timeout: self
503 .start_to_close_timeout
504 .and_then(|duration| duration.try_into().ok()),
505 }),
506 self.summary,
507 None,
508 )
509 }
510}
511
512#[derive(Default, Debug, Clone, bon::Builder)]
514#[non_exhaustive]
515pub struct ChildWorkflowOptions {
516 pub workflow_id: Option<String>,
518 pub task_queue: Option<String>,
522 #[builder(default)]
524 pub cancel_type: ChildWorkflowCancellationType,
525 #[builder(default)]
527 pub parent_close_policy: ParentClosePolicy,
528 pub static_summary: Option<String>,
530 pub static_details: Option<String>,
532 #[builder(default)]
534 pub id_reuse_policy: WorkflowIdReusePolicy,
535 pub execution_timeout: Option<Duration>,
537 pub run_timeout: Option<Duration>,
539 pub task_timeout: Option<Duration>,
541 pub cron_schedule: Option<String>,
543 pub search_attributes: Option<SearchAttributes>,
545 pub priority: Option<Priority>,
547}
548
549impl ChildWorkflowOptions {
550 pub fn workflow_id(workflow_id: String) -> Self {
554 Self::builder().workflow_id(workflow_id).build()
555 }
556
557 pub(crate) fn into_command(
558 self,
559 seq: u32,
560 workflow_type: String,
561 args: Vec<Payload>,
562 headers: HashMap<String, Payload>,
563 workflow_id: String,
564 ) -> WorkflowCommand {
565 command_with_metadata(
566 workflow_command::Variant::StartChildWorkflowExecution(StartChildWorkflowExecution {
567 seq,
568 workflow_type,
569 workflow_id,
570 task_queue: self.task_queue.unwrap_or_default(),
571 input: args,
572 headers,
573 cancellation_type: ProtoChildWorkflowCancellationType::from(self.cancel_type)
574 .into(),
575 parent_close_policy: ProtoParentClosePolicy::from(self.parent_close_policy).into(),
576 workflow_id_reuse_policy: ProtoWorkflowIdReusePolicy::from(
577 match self.id_reuse_policy {
578 WorkflowIdReusePolicy::Unspecified => WorkflowIdReusePolicy::AllowDuplicate,
579 policy => policy,
580 },
581 )
582 .into(),
583 workflow_execution_timeout: self
584 .execution_timeout
585 .and_then(|duration| duration.try_into().ok()),
586 workflow_run_timeout: self
587 .run_timeout
588 .and_then(|duration| duration.try_into().ok()),
589 workflow_task_timeout: self
590 .task_timeout
591 .and_then(|duration| duration.try_into().ok()),
592 cron_schedule: self.cron_schedule.unwrap_or_default(),
593 search_attributes: self.search_attributes.map(|t| t.into_proto()),
594 priority: self.priority.map(Into::into),
595 ..Default::default()
596 }),
597 self.static_summary,
598 self.static_details,
599 )
600 }
601}
602
603#[derive(Debug)]
605pub struct Signal {
606 pub signal_name: String,
608 pub data: SignalData,
610}
611
612impl Signal {
613 pub fn new(
615 name: impl Into<String>,
616 input: impl IntoIterator<Item = impl Into<Payload>>,
617 ) -> Self {
618 Self {
619 signal_name: name.into(),
620 data: SignalData::new(input),
621 }
622 }
623
624 pub(crate) fn into_invocation(self) -> SignalWorkflow {
625 SignalWorkflow {
626 signal_name: self.signal_name,
627 input: self.data.input,
628 identity: String::new(),
629 headers: self.data.headers,
630 }
631 }
632}
633
634#[derive(Default, Debug)]
636pub struct SignalData {
637 pub input: Vec<Payload>,
639 pub headers: HashMap<String, Payload>,
641}
642
643impl SignalData {
644 pub fn new(input: impl IntoIterator<Item = impl Into<Payload>>) -> Self {
646 Self {
647 input: input.into_iter().map(Into::into).collect(),
648 headers: HashMap::new(),
649 }
650 }
651
652 pub fn with_header(
654 &mut self,
655 key: impl Into<String>,
656 payload: impl Into<Payload>,
657 ) -> &mut Self {
658 self.headers.insert(key.into(), payload.into());
659 self
660 }
661}
662
663#[derive(Default, Debug, Clone)]
665pub struct TimerOptions {
666 pub duration: Duration,
668 pub summary: Option<String>,
670}
671
672impl From<Duration> for TimerOptions {
673 fn from(duration: Duration) -> Self {
674 TimerOptions {
675 duration,
676 ..Default::default()
677 }
678 }
679}
680
681impl TimerOptions {
682 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
683 command_with_metadata(
684 workflow_command::Variant::StartTimer(StartTimer {
685 seq,
686 start_to_fire_timeout: Some(
687 self.duration
688 .try_into()
689 .expect("workflow timer timeout must fit into protobuf duration"),
690 ),
691 }),
692 self.summary,
693 None,
694 )
695 }
696}
697
698#[derive(Default, Debug, Clone)]
700pub struct NexusOperationOptions {
701 pub endpoint: String,
703 pub service: String,
705 pub operation: String,
707 pub input: Option<Payload>,
712 pub schedule_to_close_timeout: Option<Duration>,
716 pub nexus_header: HashMap<String, String>,
723 pub cancellation_type: Option<NexusOperationCancellationType>,
725 pub schedule_to_start_timeout: Option<Duration>,
731 pub start_to_close_timeout: Option<Duration>,
738}
739
740impl NexusOperationOptions {
741 pub(crate) fn into_command(self, seq: u32) -> WorkflowCommand {
742 workflow_command::Variant::ScheduleNexusOperation(ScheduleNexusOperation {
743 seq,
744 endpoint: self.endpoint,
745 service: self.service,
746 operation: self.operation,
747 input: self.input,
748 schedule_to_close_timeout: self
749 .schedule_to_close_timeout
750 .and_then(|duration| duration.try_into().ok()),
751 schedule_to_start_timeout: self
752 .schedule_to_start_timeout
753 .and_then(|duration| duration.try_into().ok()),
754 start_to_close_timeout: self
755 .start_to_close_timeout
756 .and_then(|duration| duration.try_into().ok()),
757 nexus_header: self.nexus_header,
758 cancellation_type: ProtoNexusOperationCancellationType::from(
759 self.cancellation_type
760 .unwrap_or(NexusOperationCancellationType::WaitCancellationCompleted),
761 )
762 .into(),
763 })
764 .into()
765 }
766}
767
768#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
770#[non_exhaustive]
771pub enum ContinueAsNewVersioningBehavior {
772 #[default]
774 Unspecified,
775 AutoUpgrade,
777 UseRampingVersion,
779}
780
781impl From<ContinueAsNewVersioningBehavior> for ProtoContinueAsNewVersioningBehavior {
782 fn from(value: ContinueAsNewVersioningBehavior) -> Self {
783 match value {
784 ContinueAsNewVersioningBehavior::Unspecified => {
785 ProtoContinueAsNewVersioningBehavior::Unspecified
786 }
787 ContinueAsNewVersioningBehavior::AutoUpgrade => {
788 ProtoContinueAsNewVersioningBehavior::AutoUpgrade
789 }
790 ContinueAsNewVersioningBehavior::UseRampingVersion => {
791 ProtoContinueAsNewVersioningBehavior::UseRampingVersion
792 }
793 }
794 }
795}
796
797impl From<ProtoContinueAsNewVersioningBehavior> for ContinueAsNewVersioningBehavior {
798 fn from(value: ProtoContinueAsNewVersioningBehavior) -> Self {
799 match value {
800 ProtoContinueAsNewVersioningBehavior::Unspecified => {
801 ContinueAsNewVersioningBehavior::Unspecified
802 }
803 ProtoContinueAsNewVersioningBehavior::AutoUpgrade => {
804 ContinueAsNewVersioningBehavior::AutoUpgrade
805 }
806 ProtoContinueAsNewVersioningBehavior::UseRampingVersion => {
807 ContinueAsNewVersioningBehavior::UseRampingVersion
808 }
809 }
810 }
811}
812
813#[derive(Default, Debug, bon::Builder)]
817#[non_exhaustive]
818pub struct ContinueAsNewOptions {
819 pub workflow_type: Option<String>,
821 pub task_queue: Option<String>,
823 pub run_timeout: Option<Duration>,
825 pub task_timeout: Option<Duration>,
827 pub backoff_start_interval: Option<Duration>,
829 pub memo: Option<MemoValues>,
831 pub search_attributes: Option<SearchAttributes>,
834 #[builder(into)]
836 pub retry_policy: Option<RetryPolicy>,
837 pub versioning_intent: Option<VersioningIntent>,
839 pub initial_versioning_behavior: Option<ContinueAsNewVersioningBehavior>,
845}
846
847impl ContinueAsNewOptions {
848 pub(crate) fn into_request(
849 self,
850 workflow_type: String,
851 arguments: Vec<Payload>,
852 headers: HashMap<String, Payload>,
853 payload_converter: &PayloadConverter,
854 ) -> Result<ContinueAsNewRequest, PayloadConversionError> {
855 let memo = self
856 .memo
857 .map(|memo| memo.encode(payload_converter))
858 .transpose()?
859 .unwrap_or_default();
860 Ok(ContinueAsNewWorkflowExecution {
861 workflow_type: self.workflow_type.unwrap_or(workflow_type),
862 task_queue: self.task_queue.unwrap_or_default(),
863 arguments,
864 workflow_run_timeout: self
865 .run_timeout
866 .and_then(|duration| duration.try_into().ok()),
867 workflow_task_timeout: self
868 .task_timeout
869 .and_then(|duration| duration.try_into().ok()),
870 backoff_start_interval: self
871 .backoff_start_interval
872 .and_then(|duration| duration.try_into().ok()),
873 memo,
874 headers,
875 search_attributes: self.search_attributes.map(|t| t.into_proto()),
876 retry_policy: self.retry_policy.map(Into::into),
877 versioning_intent: ProtoVersioningIntent::from(
878 self.versioning_intent
879 .unwrap_or(VersioningIntent::Unspecified),
880 )
881 .into(),
882 initial_versioning_behavior: ProtoContinueAsNewVersioningBehavior::from(
883 self.initial_versioning_behavior
884 .unwrap_or(ContinueAsNewVersioningBehavior::Unspecified),
885 )
886 .into(),
887 })
888 }
889}
890
891fn command_with_metadata(
892 variant: workflow_command::Variant,
893 summary: Option<String>,
894 details: Option<String>,
895) -> WorkflowCommand {
896 WorkflowCommand {
897 variant: Some(variant),
898 user_metadata: string_user_metadata(summary, details),
899 }
900}
901
902fn string_user_metadata(summary: Option<String>, details: Option<String>) -> Option<UserMetadata> {
903 if summary.is_none() && details.is_none() {
904 return None;
905 }
906 let converter = PayloadConverter::default();
907 let context = SerializationContext {
908 data: &SerializationContextData::Workflow,
909 converter: &converter,
910 };
911 Some(UserMetadata {
912 summary: summary.map(|value| {
913 converter
914 .to_payload(&context, &value)
915 .expect("String-to-JSON payload serialization is infallible")
916 }),
917 details: details.map(|value| {
918 converter
919 .to_payload(&context, &value)
920 .expect("String-to-JSON payload serialization is infallible")
921 }),
922 })
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928
929 #[test]
930 fn activity_cancellation_default_preserves_sdk_behavior() {
931 assert_eq!(
932 ActivityCancellationType::default(),
933 ActivityCancellationType::TryCancel
934 );
935 }
936
937 #[test]
938 fn child_workflow_cancellation_defaults_to_wait_for_completion() {
939 assert_eq!(
940 ChildWorkflowOptions::default().cancel_type,
941 ChildWorkflowCancellationType::WaitCancellationCompleted
942 );
943 let command = ChildWorkflowOptions::default().into_command(
944 1,
945 "child".to_string(),
946 vec![],
947 HashMap::new(),
948 "child-id".to_string(),
949 );
950 let Some(workflow_command::Variant::StartChildWorkflowExecution(command)) = command.variant
951 else {
952 panic!("expected StartChildWorkflowExecution command");
953 };
954 assert_eq!(
955 command.cancellation_type,
956 ProtoChildWorkflowCancellationType::WaitCancellationCompleted as i32
957 );
958 }
959
960 #[test]
961 fn other_policy_defaults_preserve_sdk_behavior() {
962 assert_eq!(ParentClosePolicy::default(), ParentClosePolicy::Unspecified);
963 assert_eq!(
964 WorkflowIdReusePolicy::default(),
965 WorkflowIdReusePolicy::Unspecified
966 );
967 assert_eq!(VersioningIntent::default(), VersioningIntent::Unspecified);
968 assert_eq!(
969 NexusOperationCancellationType::default(),
970 NexusOperationCancellationType::WaitCancellationCompleted
971 );
972 }
973
974 #[test]
975 fn continue_as_new_options_maps_backoff_start_interval_to_request() {
976 let req = ContinueAsNewOptions {
977 backoff_start_interval: Some(Duration::from_secs(7)),
978 versioning_intent: Some(VersioningIntent::Compatible),
979 ..Default::default()
980 }
981 .into_request(
982 "test-workflow".to_string(),
983 vec![],
984 HashMap::new(),
985 &PayloadConverter::default(),
986 )
987 .unwrap();
988
989 let backoff = req
990 .backoff_start_interval
991 .expect("backoff_start_interval should be set");
992 assert_eq!(backoff.seconds, 7);
993 assert_eq!(backoff.nanos, 0);
994 assert_eq!(
995 req.versioning_intent,
996 ProtoVersioningIntent::Compatible as i32
997 );
998 }
999
1000 #[test]
1001 fn activity_options_with_start_to_close_timeout_wrapper_supports_builder_chaining() {
1002 let opts = ActivityOptions::with_start_to_close_timeout(Duration::from_secs(5))
1003 .heartbeat_timeout(Duration::from_secs(2))
1004 .build();
1005
1006 assert_eq!(
1007 opts.close_timeouts,
1008 ActivityCloseTimeouts::StartToClose(Duration::from_secs(5))
1009 );
1010 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
1011 }
1012
1013 #[test]
1014 fn activity_options_with_schedule_to_close_timeout_wrapper_supports_builder_chaining() {
1015 let opts = ActivityOptions::with_schedule_to_close_timeout(Duration::from_secs(5))
1016 .heartbeat_timeout(Duration::from_secs(2))
1017 .build();
1018
1019 assert_eq!(
1020 opts.close_timeouts,
1021 ActivityCloseTimeouts::ScheduleToClose(Duration::from_secs(5))
1022 );
1023 assert_eq!(opts.heartbeat_timeout, Some(Duration::from_secs(2)));
1024 }
1025
1026 #[test]
1027 fn activity_options_both_close_timeouts_map_to_command() {
1028 let req = ActivityOptions::with_close_timeouts(ActivityCloseTimeouts::Both {
1029 start_to_close: Duration::from_secs(3),
1030 schedule_to_close: Duration::from_secs(8),
1031 })
1032 .cancellation_type(ActivityCancellationType::Abandon)
1033 .build()
1034 .into_command(7, "test".to_string(), vec![], HashMap::new());
1035 let Some(workflow_command::Variant::ScheduleActivity(req)) = req.variant else {
1036 panic!("expected ScheduleActivity command");
1037 };
1038 assert_eq!(req.start_to_close_timeout.unwrap().seconds, 3);
1039 assert_eq!(req.schedule_to_close_timeout.unwrap().seconds, 8);
1040 assert_eq!(
1041 req.cancellation_type,
1042 ProtoActivityCancellationType::Abandon as i32
1043 );
1044 }
1045
1046 #[test]
1047 fn child_workflow_run_timeout_uses_run_timeout_field() {
1048 let opts = ChildWorkflowOptions {
1049 workflow_id: Some("test-wf".to_string()),
1050 cancel_type: ChildWorkflowCancellationType::WaitCancellationRequested,
1051 parent_close_policy: ParentClosePolicy::RequestCancel,
1052 id_reuse_policy: WorkflowIdReusePolicy::RejectDuplicate,
1053 execution_timeout: Some(Duration::from_secs(60)),
1054 run_timeout: Some(Duration::from_secs(10)),
1055 ..Default::default()
1056 };
1057 let command = opts.into_command(
1058 1,
1059 "TestWorkflow".to_string(),
1060 vec![],
1061 HashMap::new(),
1062 "test-wf".into(),
1063 );
1064 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1065 else {
1066 panic!("expected StartChildWorkflowExecution command");
1067 };
1068 let exec_timeout = req.workflow_execution_timeout.unwrap();
1069 let run_timeout = req.workflow_run_timeout.unwrap();
1070 assert_eq!(exec_timeout.seconds, 60);
1071 assert_eq!(run_timeout.seconds, 10);
1072 assert_eq!(
1073 req.cancellation_type,
1074 ProtoChildWorkflowCancellationType::WaitCancellationRequested as i32
1075 );
1076 assert_eq!(
1077 req.parent_close_policy,
1078 ProtoParentClosePolicy::RequestCancel as i32
1079 );
1080 assert_eq!(
1081 req.workflow_id_reuse_policy,
1082 ProtoWorkflowIdReusePolicy::RejectDuplicate as i32
1083 );
1084 }
1085
1086 #[test]
1087 fn child_workflow_run_timeout_none_when_unset() {
1088 let opts = ChildWorkflowOptions {
1089 workflow_id: Some("test-wf".to_string()),
1090 execution_timeout: Some(Duration::from_secs(60)),
1091 ..Default::default()
1092 };
1093 let command = opts.into_command(
1094 1,
1095 "TestWorkflow".to_string(),
1096 vec![],
1097 HashMap::new(),
1098 "test-wf".into(),
1099 );
1100 let Some(workflow_command::Variant::StartChildWorkflowExecution(req)) = command.variant
1101 else {
1102 panic!("expected StartChildWorkflowExecution command");
1103 };
1104 let exec_timeout = req.workflow_execution_timeout.unwrap();
1105 assert_eq!(exec_timeout.seconds, 60);
1106 assert!(req.workflow_run_timeout.is_none());
1107 }
1108}