Skip to main content

dex_sdk/
step_options.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Super Durable Source License 1.0.
4// You may not use this file except in compliance with the License.
5// See the LICENSE file in the repository root.
6//
7// SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
8
9use std::marker::PhantomData;
10use std::time::Duration;
11
12use crate::attribute::AttributeLock;
13use crate::{RetryPolicy, Step, Value};
14
15/// Configures one Step's handler execution and persistence behavior.
16///
17/// New options preserve server timeout, retry, and durability defaults. `wait_for` failures fail the
18/// Flow by default. Attribute locks apply only to the matching handler call. The type system checks
19/// that an execute-failure recovery Step accepts the same input type.
20///
21/// # Examples
22///
23/// ```
24/// use dex_sdk::{Attribute, RetryPolicy, StepOptions, WaitForFailurePolicy};
25/// use std::time::Duration;
26///
27/// let balance = Attribute::<i64>::new("balance");
28/// let options = StepOptions::<String>::new()
29///     .execute_method_timeout(Duration::from_secs(30))
30///     .execute_retry(RetryPolicy::new().maximum_attempts(3))
31///     .wait_for_failure(WaitForFailurePolicy::Proceed)
32///     .execute_lock(balance.lock());
33/// ```
34pub struct StepOptions<Input> {
35    pub(crate) wait_for_method_timeout: Option<Duration>,
36    pub(crate) execute_method_timeout: Option<Duration>,
37    pub(crate) heartbeat_timeout: Option<Duration>,
38    pub(crate) wait_for_retry: Option<RetryPolicy>,
39    pub(crate) execute_retry: Option<RetryPolicy>,
40    pub(crate) wait_for_failure: WaitForFailurePolicy,
41    pub(crate) wait_for_durability: StepDurability,
42    pub(crate) execute_durability: StepDurability,
43    pub(crate) wait_for_locks: Vec<AttributeLock>,
44    pub(crate) execute_locks: Vec<AttributeLock>,
45    pub(crate) execute_failure_step: Option<&'static str>,
46    marker: PhantomData<fn(Input)>,
47}
48
49#[derive(Clone)]
50pub(crate) struct ErasedStepOptions {
51    pub(crate) wait_for_method_timeout: Option<Duration>,
52    pub(crate) execute_method_timeout: Option<Duration>,
53    pub(crate) heartbeat_timeout: Option<Duration>,
54    pub(crate) wait_for_retry: Option<RetryPolicy>,
55    pub(crate) execute_retry: Option<RetryPolicy>,
56    pub(crate) wait_for_failure: WaitForFailurePolicy,
57    pub(crate) wait_for_durability: StepDurability,
58    pub(crate) execute_durability: StepDurability,
59    pub(crate) wait_for_locks: Vec<AttributeLock>,
60    pub(crate) execute_locks: Vec<AttributeLock>,
61    pub(crate) execute_failure_step: Option<&'static str>,
62}
63
64impl<Input> From<StepOptions<Input>> for ErasedStepOptions {
65    fn from(options: StepOptions<Input>) -> Self {
66        Self {
67            wait_for_method_timeout: options.wait_for_method_timeout,
68            execute_method_timeout: options.execute_method_timeout,
69            heartbeat_timeout: options.heartbeat_timeout,
70            wait_for_retry: options.wait_for_retry,
71            execute_retry: options.execute_retry,
72            wait_for_failure: options.wait_for_failure,
73            wait_for_durability: options.wait_for_durability,
74            execute_durability: options.execute_durability,
75            wait_for_locks: options.wait_for_locks,
76            execute_locks: options.execute_locks,
77            execute_failure_step: options.execute_failure_step,
78        }
79    }
80}
81
82impl<Input: Value> StepOptions<Input> {
83    /// Creates options that preserve server defaults and fail on exhausted `wait_for` retries.
84    pub fn new() -> Self {
85        Self {
86            wait_for_method_timeout: None,
87            execute_method_timeout: None,
88            heartbeat_timeout: None,
89            wait_for_retry: None,
90            execute_retry: None,
91            wait_for_failure: WaitForFailurePolicy::FailFlow,
92            wait_for_durability: StepDurability::Default,
93            execute_durability: StepDurability::Default,
94            wait_for_locks: Vec::new(),
95            execute_locks: Vec::new(),
96            execute_failure_step: None,
97            marker: PhantomData,
98        }
99    }
100
101    /// Sets the maximum duration of one `wait_for` handler attempt.
102    pub fn wait_for_method_timeout(mut self, value: Duration) -> Self {
103        self.wait_for_method_timeout = Some(value);
104        self
105    }
106
107    /// Sets the maximum duration of one `execute` handler attempt.
108    pub fn execute_method_timeout(mut self, value: Duration) -> Self {
109        self.execute_method_timeout = Some(value);
110        self
111    }
112
113    /// Enables cooperative cancellation heartbeats for regular `wait_for` and `execute` activities.
114    ///
115    /// Zero disables heartbeats. Positive values must be whole seconds within the int32 range.
116    /// Local activities ignore this option; an asynchronous fallback to regular activity uses it.
117    pub fn heartbeat_timeout(mut self, value: Duration) -> Self {
118        self.heartbeat_timeout = Some(value);
119        self
120    }
121
122    /// Sets the retry policy for failed `wait_for` attempts.
123    pub fn wait_for_retry(mut self, value: RetryPolicy) -> Self {
124        self.wait_for_retry = Some(value);
125        self
126    }
127
128    /// Sets the retry policy for failed `execute` attempts.
129    pub fn execute_retry(mut self, value: RetryPolicy) -> Self {
130        self.execute_retry = Some(value);
131        self
132    }
133
134    /// Selects whether exhausted `wait_for` retries fail or advance the Flow.
135    pub fn wait_for_failure(mut self, value: WaitForFailurePolicy) -> Self {
136        self.wait_for_failure = value;
137        self
138    }
139
140    /// Routes exhausted `execute` failures to a recovery Step with the same input type.
141    pub fn on_execute_failure_proceed_to<RecoveryStep>(mut self, step: &RecoveryStep) -> Self
142    where
143        RecoveryStep: Step<Input = Input>,
144    {
145        self.execute_failure_step = Some(step.step_type());
146        self
147    }
148
149    /// Overrides persistence durability for writes staged by `wait_for`.
150    pub fn wait_for_durability(mut self, value: StepDurability) -> Self {
151        self.wait_for_durability = value;
152        self
153    }
154
155    /// Overrides persistence durability for writes staged by `execute`.
156    pub fn execute_durability(mut self, value: StepDurability) -> Self {
157        self.execute_durability = value;
158        self
159    }
160
161    /// Adds an Attribute lock held for the `wait_for` invocation.
162    pub fn wait_for_lock(mut self, value: AttributeLock) -> Self {
163        self.wait_for_locks.push(value);
164        self
165    }
166
167    /// Adds an Attribute lock held for the `execute` invocation.
168    pub fn execute_lock(mut self, value: AttributeLock) -> Self {
169        self.execute_locks.push(value);
170        self
171    }
172}
173
174impl<Input: Value> Default for StepOptions<Input> {
175    fn default() -> Self {
176        Self::new()
177    }
178}
179
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181/// Selects the terminal behavior after `wait_for` exhausts its retry policy.
182pub enum WaitForFailurePolicy {
183    /// Fails the Flow.
184    FailFlow,
185    /// Skips the failed wait and invokes `execute` with failure visible in [`crate::Context`].
186    Proceed,
187}
188
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
190/// Controls when staged Step persistence must be durably acknowledged.
191pub enum StepDurability {
192    /// Uses the Flow or server default.
193    Default,
194    /// Waits for durable persistence before completing the handler request.
195    Sync,
196    /// Allows asynchronous persistence after accepting the handler result.
197    Async,
198}