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    /// Sets the heartbeat timeout for regular `wait_for` and `execute` activities.
114    ///
115    /// Zero selects the server default of one minute. Positive values must be whole seconds and at
116    /// least the server-configured minimum, which defaults to ten seconds. Local activities ignore
117    /// this option; an asynchronous fallback to a regular activity uses it.
118    pub fn heartbeat_timeout(mut self, value: Duration) -> Self {
119        self.heartbeat_timeout = Some(value);
120        self
121    }
122
123    /// Sets the retry policy for failed `wait_for` attempts.
124    pub fn wait_for_retry(mut self, value: RetryPolicy) -> Self {
125        self.wait_for_retry = Some(value);
126        self
127    }
128
129    /// Sets the retry policy for failed `execute` attempts.
130    pub fn execute_retry(mut self, value: RetryPolicy) -> Self {
131        self.execute_retry = Some(value);
132        self
133    }
134
135    /// Selects whether exhausted `wait_for` retries fail or advance the Flow.
136    pub fn wait_for_failure(mut self, value: WaitForFailurePolicy) -> Self {
137        self.wait_for_failure = value;
138        self
139    }
140
141    /// Routes exhausted `execute` failures to a recovery Step with the same input type.
142    pub fn on_execute_failure_proceed_to<RecoveryStep>(mut self, step: &RecoveryStep) -> Self
143    where
144        RecoveryStep: Step<Input = Input>,
145    {
146        self.execute_failure_step = Some(step.step_type());
147        self
148    }
149
150    /// Overrides persistence durability for writes staged by `wait_for`.
151    pub fn wait_for_durability(mut self, value: StepDurability) -> Self {
152        self.wait_for_durability = value;
153        self
154    }
155
156    /// Overrides persistence durability for writes staged by `execute`.
157    pub fn execute_durability(mut self, value: StepDurability) -> Self {
158        self.execute_durability = value;
159        self
160    }
161
162    /// Adds an Attribute lock held for the `wait_for` invocation.
163    pub fn wait_for_lock(mut self, value: AttributeLock) -> Self {
164        self.wait_for_locks.push(value);
165        self
166    }
167
168    /// Adds an Attribute lock held for the `execute` invocation.
169    pub fn execute_lock(mut self, value: AttributeLock) -> Self {
170        self.execute_locks.push(value);
171        self
172    }
173}
174
175impl<Input: Value> Default for StepOptions<Input> {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182/// Selects the terminal behavior after `wait_for` exhausts its retry policy.
183pub enum WaitForFailurePolicy {
184    /// Fails the Flow.
185    FailFlow,
186    /// Skips the failed wait and invokes `execute` with failure visible in [`crate::Context`].
187    Proceed,
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191/// Controls when staged Step persistence must be durably acknowledged.
192pub enum StepDurability {
193    /// Uses the Flow or server default.
194    Default,
195    /// Waits for durable persistence before completing the handler request.
196    Sync,
197    /// Allows asynchronous persistence after accepting the handler result.
198    Async,
199}