ironflow_engine/config/workflow.rs
1//! Configuration for workflow (sub-workflow) steps.
2
3use std::ops::Not;
4
5use ironflow_core::retry::RetryPolicy;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Configuration for invoking a registered workflow as a sub-step.
10///
11/// The engine will look up the handler by [`workflow_name`](WorkflowStepConfig::workflow_name)
12/// and execute it as a child run with its own steps and lifecycle.
13///
14/// # Examples
15///
16/// ```
17/// use ironflow_engine::config::WorkflowStepConfig;
18/// use serde_json::json;
19///
20/// let config = WorkflowStepConfig::new("build", json!({"branch": "main"}));
21/// assert_eq!(config.workflow_name, "build");
22/// ```
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WorkflowStepConfig {
25 /// Name of the registered workflow handler to invoke.
26 pub workflow_name: String,
27 /// Payload to pass to the child workflow run.
28 pub payload: Value,
29 /// Optional step-level retry policy.
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub retry: Option<RetryPolicy>,
32 /// Tolerate a failed child: the step completes with the child's failure
33 /// in its output instead of failing the parent.
34 #[serde(default, skip_serializing_if = "Not::not")]
35 pub allow_failure: bool,
36}
37
38impl WorkflowStepConfig {
39 /// Create a new workflow step config.
40 ///
41 /// # Examples
42 ///
43 /// ```
44 /// use ironflow_engine::config::WorkflowStepConfig;
45 /// use serde_json::json;
46 ///
47 /// let config = WorkflowStepConfig::new("deploy", json!({}));
48 /// assert_eq!(config.workflow_name, "deploy");
49 /// ```
50 pub fn new(workflow_name: &str, payload: Value) -> Self {
51 Self {
52 workflow_name: workflow_name.to_string(),
53 payload,
54 retry: None,
55 allow_failure: false,
56 }
57 }
58
59 /// Set a step-level retry policy.
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use ironflow_core::retry::RetryPolicy;
65 /// use ironflow_engine::config::WorkflowStepConfig;
66 /// use serde_json::json;
67 ///
68 /// let config = WorkflowStepConfig::new("build", json!({}))
69 /// .retry_policy(RetryPolicy::new(3));
70 /// assert!(config.retry.is_some());
71 /// ```
72 pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
73 self.retry = Some(policy);
74 self
75 }
76
77 /// Tolerate a failed child run: the step completes with a
78 /// [`SubWorkflowOutput`](crate::executor::SubWorkflowOutput) reporting the
79 /// failure and the parent run ends as `Warning`.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use ironflow_engine::config::WorkflowStepConfig;
85 /// use serde_json::json;
86 ///
87 /// let config = WorkflowStepConfig::new("build", json!({})).allow_failure();
88 /// assert!(config.allow_failure);
89 /// ```
90 pub fn allow_failure(mut self) -> Self {
91 self.allow_failure = true;
92 self
93 }
94}
95
96/// Options of a sub-workflow step started with
97/// [`workflow_with`](crate::context::WorkflowContext::workflow_with).
98///
99/// # Examples
100///
101/// ```
102/// use ironflow_engine::config::WorkflowOptions;
103///
104/// let options = WorkflowOptions::new().allow_failure();
105/// assert!(options.allow_failure);
106/// assert!(!WorkflowOptions::default().allow_failure);
107/// ```
108#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
109pub struct WorkflowOptions {
110 /// Tolerate a failed child run instead of failing the parent.
111 pub allow_failure: bool,
112}
113
114impl WorkflowOptions {
115 /// Create options with every flag off.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use ironflow_engine::config::WorkflowOptions;
121 ///
122 /// assert_eq!(WorkflowOptions::new(), WorkflowOptions::default());
123 /// ```
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 /// Tolerate a failed child run: the step completes with the failure in its
129 /// output and the parent run ends as `Warning`. A suspension is never
130 /// tolerated.
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// use ironflow_engine::config::WorkflowOptions;
136 ///
137 /// assert!(WorkflowOptions::new().allow_failure().allow_failure);
138 /// ```
139 pub fn allow_failure(mut self) -> Self {
140 self.allow_failure = true;
141 self
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use serde_json::json;
149
150 #[test]
151 fn new_sets_fields() {
152 let config = WorkflowStepConfig::new("build", json!({"key": "val"}));
153 assert_eq!(config.workflow_name, "build");
154 assert_eq!(config.payload["key"], "val");
155 }
156
157 #[test]
158 fn serde_roundtrip() {
159 let config = WorkflowStepConfig::new("deploy", json!({"env": "prod"}));
160 let json = serde_json::to_string(&config).unwrap();
161 let back: WorkflowStepConfig = serde_json::from_str(&json).unwrap();
162 assert_eq!(back.workflow_name, "deploy");
163 assert_eq!(back.payload["env"], "prod");
164 }
165
166 #[test]
167 fn a_config_predating_retry_still_deserializes() {
168 let config: WorkflowStepConfig =
169 serde_json::from_str(r#"{"workflow_name":"build","payload":{"key":"val"}}"#)
170 .expect("deserialize");
171 assert!(config.retry.is_none());
172 }
173
174 #[test]
175 fn allow_failure_defaults_to_false() {
176 let config = WorkflowStepConfig::new("build", json!({}));
177 assert!(!config.allow_failure);
178 }
179
180 #[test]
181 fn allow_failure_is_omitted_from_json_when_false() {
182 let config = WorkflowStepConfig::new("build", json!({}));
183 let value = serde_json::to_value(&config).expect("serialize");
184 assert!(value.get("allow_failure").is_none());
185 }
186
187 #[test]
188 fn allow_failure_roundtrip() {
189 let config = WorkflowStepConfig::new("build", json!({})).allow_failure();
190 let json = serde_json::to_string(&config).expect("serialize");
191 let back: WorkflowStepConfig = serde_json::from_str(&json).expect("deserialize");
192 assert!(back.allow_failure);
193 }
194
195 #[test]
196 fn options_builder_sets_allow_failure() {
197 assert!(!WorkflowOptions::new().allow_failure);
198 assert!(WorkflowOptions::new().allow_failure().allow_failure);
199 }
200
201 #[test]
202 fn retry_policy_roundtrip() {
203 let config = WorkflowStepConfig::new("deploy", json!({})).retry_policy(RetryPolicy::new(3));
204 let json = serde_json::to_string(&config).expect("serialize");
205 let back: WorkflowStepConfig = serde_json::from_str(&json).expect("deserialize");
206 assert_eq!(back.retry.as_ref().unwrap().max_retries(), 3);
207 }
208}