Skip to main content

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}