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}