Skip to main content

dex_sdk/
sub_flow.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use 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-Sustainable-Use-1.0
8
9use std::any::TypeId;
10use std::time::Duration;
11
12use crate::start_flow_options::InitialAttribute;
13use crate::wait::SubFlowDefinition;
14use crate::{
15    Attribute, AttributeMap, Condition, Context, Flow, FlowConfig, FlowResult,
16    FlowTimeoutHandlerOptions, FlowTimeoutPolicy, HandlerResult, RetryPolicy, SdkResult, Value,
17    value_mapper,
18};
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21/// Controls how a generated SubFlow Flow ID resolves an existing execution.
22pub enum SubFlowReusePolicy {
23    /// Attaches to a running execution or returns its existing terminal result.
24    Attach,
25    /// Restarts abnormal executions, attaches while running, and returns completed results.
26    RestartIfPreviousExitsAbnormally,
27    /// Replaces any different existing execution, including a running one.
28    AlwaysRestart,
29}
30
31#[derive(Clone)]
32/// Configures one durable SubFlow Condition.
33///
34/// Changed in 1.25.0: the SubFlow always selects pull Workers. Other omitted
35/// configuration fields inherit the parent's effective settings. Dex generates its Flow ID and
36/// request ID. Builder methods preserve normal Flow start defaults when omitted.
37/// Matching generated request IDs always attach to an existing child, regardless of reuse policy.
38/// For different request IDs, the reuse policy decides whether to attach or restart.
39/// Completing an any-of wait does not stop non-winning SubFlows; use their generated Flow IDs
40/// with [`crate::Client::stop_flow`] when those children should stop.
41pub struct SubFlowOptions {
42    pub(crate) timeout: Option<Duration>,
43    pub(crate) timeout_policy: FlowTimeoutPolicy,
44    pub(crate) timeout_handler_options: Option<FlowTimeoutHandlerOptions>,
45    pub(crate) start_delay: Option<Duration>,
46    pub(crate) retry_policy: Option<RetryPolicy>,
47    pub(crate) config_override: Option<FlowConfig>,
48    pub(crate) attributes: Vec<InitialAttribute>,
49    pub(crate) reuse_policy: SubFlowReusePolicy,
50    pub(crate) condition_id: Option<String>,
51}
52
53impl SubFlowOptions {
54    /// Creates options using the abnormal-restart reuse policy and normal start defaults.
55    pub fn new() -> Self {
56        Self {
57            timeout: None,
58            timeout_policy: FlowTimeoutPolicy::Default,
59            timeout_handler_options: None,
60            start_delay: None,
61            retry_policy: None,
62            config_override: None,
63            attributes: Vec::new(),
64            reuse_policy: SubFlowReusePolicy::RestartIfPreviousExitsAbnormally,
65            condition_id: None,
66        }
67    }
68
69    /// Sets the maximum total SubFlow execution duration.
70    pub fn timeout(mut self, value: Duration) -> Self {
71        self.timeout = Some(value);
72        self
73    }
74
75    /// Selects what Dex does when the positive soft SubFlow timeout expires.
76    pub fn timeout_policy(mut self, value: FlowTimeoutPolicy) -> Self {
77        self.timeout_policy = value;
78        self
79    }
80
81    /// Configures execution and selective state loading for the target Flow's timeout handler.
82    pub fn timeout_handler_options(mut self, value: FlowTimeoutHandlerOptions) -> Self {
83        self.timeout_handler_options = Some(value);
84        self
85    }
86
87    /// Sets a delay before the SubFlow starting Step after server acceptance.
88    ///
89    /// The Server currently supports only a zero delay. It rejects a Step result that requests a
90    /// SubFlow with a positive delay as invalid input, so that Step attempt fails.
91    pub fn start_delay(mut self, value: Duration) -> Self {
92        self.start_delay = Some(value);
93        self
94    }
95
96    /// Sets a whole-Flow retry policy for the SubFlow.
97    ///
98    /// The Server does not support whole-Flow retry. It rejects a Step result that requests a
99    /// SubFlow with this option as invalid input, so that Step attempt fails. Configure
100    /// [`crate::RetryPolicy`] on the SubFlow's Step methods through [`crate::StepOptions`]
101    /// instead.
102    pub fn retry_policy(mut self, value: RetryPolicy) -> Self {
103        self.retry_policy = Some(value);
104        self
105    }
106
107    /// Applies fields over the inherited parent Flow configuration.
108    pub fn config_override(mut self, value: FlowConfig) -> Self {
109        self.config_override = Some(value);
110        self
111    }
112
113    /// Selects existing-execution resolution behavior.
114    pub fn reuse_policy(mut self, value: SubFlowReusePolicy) -> Self {
115        self.reuse_policy = value;
116        self
117    }
118
119    /// Assigns the stable ID required by [`crate::Wait::any_combination_of`].
120    pub fn condition_id(mut self, value: impl Into<String>) -> Self {
121        self.condition_id = Some(value.into());
122        self
123    }
124
125    /// Adds one initial singleton Attribute owned by the target SubFlow.
126    pub fn initial_attribute<T: Value>(mut self, attribute: &Attribute<T>, value: T) -> Self {
127        self.attributes
128            .push(InitialAttribute::for_attribute(attribute, value));
129        self
130    }
131
132    /// Adds one initial Attribute-map value for an instance.
133    /// Slash is prohibited in instance keys because it is a reserved character.
134    pub fn initial_attribute_map<T: Value>(
135        mut self,
136        attribute: &AttributeMap<T>,
137        instance: &str,
138        value: T,
139    ) -> Self {
140        self.attributes.push(InitialAttribute::for_attribute_map(
141            attribute, instance, value,
142        ));
143        self
144    }
145}
146
147impl Default for SubFlowOptions {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153/// Creates durable SubFlow Conditions and reads their Execute results.
154///
155/// Construction encodes input and returns a Condition; Dex starts or attaches to the child when
156/// the Worker submits that wait. The target Flow must be in the Worker's Registry. The parent
157/// waits durably without holding a handler thread. Inspect [`FlowResult`] for child failure;
158/// a child's abnormal terminal status is a result rather than a local value-mapping error.
159/// Result indexes are zero-based within the SubFlow Conditions, not all Condition kinds.
160///
161/// ```no_run
162/// use dex_sdk::{Flow, SdkResult, SubFlow, SubFlowOptions, Wait};
163/// use std::time::Duration;
164/// fn wait_for_child<F: Flow>(flow: &F, input: F::StartInput) -> SdkResult<Wait> {
165///     let child = SubFlow::run_with_options(flow, input,
166///         SubFlowOptions::new().timeout(Duration::from_secs(60)))?;
167///     Ok(Wait::until(child))
168/// }
169/// ```
170pub struct SubFlow;
171
172impl SubFlow {
173    /// Creates a SubFlow Condition with default options.
174    ///
175    /// # Errors
176    ///
177    /// Returns a value-mapping error when `input` cannot be encoded.
178    pub fn run<SomeFlow: Flow>(
179        flow: &SomeFlow,
180        input: SomeFlow::StartInput,
181    ) -> SdkResult<Condition> {
182        Self::run_with_options(flow, input, SubFlowOptions::new())
183    }
184
185    /// Creates a SubFlow Condition with explicit options.
186    ///
187    /// # Errors
188    ///
189    /// Returns a value-mapping error when `input` cannot be encoded. Worker mapping later validates
190    /// the exact registered Rust Flow type, starting Step, Attributes, and option durations.
191    pub fn run_with_options<SomeFlow: Flow>(
192        flow: &SomeFlow,
193        input: SomeFlow::StartInput,
194        options: SubFlowOptions,
195    ) -> SdkResult<Condition> {
196        Ok(Condition::sub_flow(SubFlowDefinition {
197            flow_type: flow.flow_type(),
198            type_id: TypeId::of::<SomeFlow>(),
199            input: value_mapper::encode(&input)?,
200            options,
201        }))
202    }
203
204    /// Returns the first SubFlow result during Step `execute`.
205    ///
206    /// # Errors
207    ///
208    /// Returns [`crate::HandlerError`] outside Execute or when the selected SubFlow result is
209    /// unavailable or malformed. See [`SubFlow`] for constructing the matching wait.
210    pub fn condition_result(context: &Context) -> HandlerResult<FlowResult> {
211        Self::condition_result_at(context, 0)
212    }
213
214    /// Returns one stable-indexed SubFlow result during Step `execute`.
215    ///
216    /// # Errors
217    ///
218    /// Returns [`crate::HandlerError`] outside Execute or when the selected SubFlow result is
219    /// unavailable or malformed. See [`SubFlow`] for constructing the matching wait.
220    pub fn condition_result_at(context: &Context, index: usize) -> HandlerResult<FlowResult> {
221        context.sub_flow_result(index)
222    }
223
224    /// Returns the generated Flow ID for the first SubFlow Condition during Step `execute`.
225    ///
226    /// # Errors
227    ///
228    /// Returns [`crate::HandlerError`] outside Execute or when the selected SubFlow result is
229    /// unavailable or malformed. See [`SubFlow`] for constructing the matching wait.
230    pub fn flow_id(context: &Context) -> HandlerResult<String> {
231        Self::flow_id_at(context, 0)
232    }
233
234    /// Returns the generated Flow ID for one stable-indexed SubFlow Condition during `execute`.
235    ///
236    /// # Errors
237    ///
238    /// Returns [`crate::HandlerError`] outside Execute or when the selected SubFlow result is
239    /// unavailable or malformed. See [`SubFlow`] for constructing the matching wait.
240    pub fn flow_id_at(context: &Context, index: usize) -> HandlerResult<String> {
241        context.sub_flow_id(index)
242    }
243}