Skip to main content

ironflow_engine/config/
mod.rs

1//! Serializable step configurations — one per operation type.
2//!
3//! These types mirror the builder options in [`ironflow_core`] operations but
4//! are fully serializable, allowing them to be stored as JSON in the database
5//! and reconstructed by the executor at runtime.
6
7mod agent;
8mod approval;
9mod artifact;
10pub mod delay;
11mod http;
12mod shell;
13mod workflow;
14
15pub use agent::AgentStepConfig;
16pub use approval::ApprovalConfig;
17pub use artifact::{ArtifactInput, ArtifactOutput};
18pub use delay::DelayConfig;
19pub use http::HttpConfig;
20pub use shell::ShellConfig;
21pub use workflow::WorkflowStepConfig;
22
23use ironflow_core::retry::RetryPolicy;
24use ironflow_store::entities::StepKind;
25use serde::{Deserialize, Serialize};
26
27/// A serializable step configuration, wrapping one of the operation-specific configs.
28///
29/// Stored as JSON in the `steps.input` column and reconstructed by the
30/// executor at runtime.
31///
32/// # Examples
33///
34/// ```
35/// use ironflow_engine::config::{StepConfig, ShellConfig};
36///
37/// let config = StepConfig::Shell(ShellConfig::new("echo hello"));
38/// let json = serde_json::to_string(&config).unwrap();
39/// assert!(json.contains("echo hello"));
40/// ```
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(tag = "type", rename_all = "snake_case")]
43pub enum StepConfig {
44    /// A shell command step.
45    Shell(ShellConfig),
46    /// An HTTP request step.
47    Http(HttpConfig),
48    /// An AI agent step.
49    Agent(AgentStepConfig),
50    /// A sub-workflow invocation step.
51    Workflow(WorkflowStepConfig),
52    /// A human approval gate step.
53    Approval(ApprovalConfig),
54    /// A timed delay/sleep step.
55    Delay(DelayConfig),
56}
57
58impl StepConfig {
59    /// Whether this step is allowed to fail without stopping the run.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use ironflow_engine::config::{StepConfig, ShellConfig};
65    ///
66    /// let config = StepConfig::Shell(ShellConfig::new("cargo clippy").allow_failure());
67    /// assert!(config.allow_failure());
68    ///
69    /// let config = StepConfig::Shell(ShellConfig::new("cargo build"));
70    /// assert!(!config.allow_failure());
71    /// ```
72    pub fn allow_failure(&self) -> bool {
73        match self {
74            StepConfig::Shell(c) => c.allow_failure,
75            StepConfig::Http(c) => c.allow_failure,
76            StepConfig::Agent(c) => c.allow_failure,
77            StepConfig::Workflow(_) | StepConfig::Approval(_) | StepConfig::Delay(_) => false,
78        }
79    }
80
81    /// Get the step-level retry policy, if any.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use ironflow_core::retry::RetryPolicy;
87    /// use ironflow_engine::config::{StepConfig, ShellConfig};
88    ///
89    /// let config = StepConfig::Shell(ShellConfig::new("echo test").retry_policy(RetryPolicy::new(3)));
90    /// assert!(config.retry().is_some());
91    ///
92    /// let config = StepConfig::Shell(ShellConfig::new("echo test"));
93    /// assert!(config.retry().is_none());
94    /// ```
95    pub fn retry(&self) -> Option<&RetryPolicy> {
96        match self {
97            StepConfig::Shell(c) => c.retry.as_ref(),
98            StepConfig::Http(c) => c.retry.as_ref(),
99            StepConfig::Agent(c) => c.retry.as_ref(),
100            StepConfig::Workflow(c) => c.retry.as_ref(),
101            StepConfig::Approval(_) | StepConfig::Delay(_) => None,
102        }
103    }
104
105    /// Get the kind of step this configuration represents.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use ironflow_engine::config::{StepConfig, ShellConfig};
111    /// use ironflow_store::entities::StepKind;
112    ///
113    /// let config = StepConfig::Shell(ShellConfig::new("echo test"));
114    /// assert_eq!(config.kind(), StepKind::Shell);
115    /// ```
116    pub fn kind(&self) -> StepKind {
117        match self {
118            StepConfig::Shell(_) => StepKind::Shell,
119            StepConfig::Http(_) => StepKind::Http,
120            StepConfig::Agent(_) => StepKind::Agent,
121            StepConfig::Workflow(_) => StepKind::Workflow,
122            StepConfig::Approval(_) => StepKind::Approval,
123            StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
124        }
125    }
126}
127
128impl From<ShellConfig> for StepConfig {
129    fn from(c: ShellConfig) -> Self {
130        StepConfig::Shell(c)
131    }
132}
133
134impl From<HttpConfig> for StepConfig {
135    fn from(c: HttpConfig) -> Self {
136        StepConfig::Http(c)
137    }
138}
139
140impl From<AgentStepConfig> for StepConfig {
141    fn from(c: AgentStepConfig) -> Self {
142        StepConfig::Agent(c)
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use ironflow_core::retry::RetryPolicy;
149
150    use super::*;
151
152    #[test]
153    fn retry_accessor_returns_policy_for_each_variant() {
154        let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
155        assert_eq!(shell.retry().unwrap().max_retries(), 2);
156
157        let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
158        assert_eq!(http.retry().unwrap().max_retries(), 3);
159
160        let workflow = StepConfig::Workflow(
161            WorkflowStepConfig::new("build", serde_json::json!({}))
162                .retry_policy(RetryPolicy::new(4)),
163        );
164        assert_eq!(workflow.retry().unwrap().max_retries(), 4);
165
166        let agent = StepConfig::Agent(AgentStepConfig::new("test"));
167        assert!(agent.retry().is_none());
168
169        let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
170        assert!(approval.retry().is_none());
171    }
172
173    #[test]
174    fn serde_roundtrip() {
175        let configs = vec![
176            StepConfig::Shell(ShellConfig::new("echo test")),
177            StepConfig::Http(HttpConfig::get("http://example.com")),
178            StepConfig::Agent(AgentStepConfig::new("summarize")),
179            StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
180            StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
181            StepConfig::Delay(DelayConfig::from_secs(60)),
182        ];
183
184        for config in configs {
185            let json = serde_json::to_string(&config).expect("serialize");
186            let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
187            let json2 = serde_json::to_string(&back).expect("serialize2");
188            assert_eq!(json, json2);
189        }
190    }
191}