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