ironflow_engine/config/
mod.rs1mod agent;
8mod approval;
9mod approval_rule;
10mod artifact;
11mod decision;
12pub mod delay;
13mod escalation;
14mod http;
15mod shell;
16mod workflow;
17
18pub use agent::AgentStepConfig;
19pub use approval::ApprovalConfig;
20pub use approval_rule::ApprovalRule;
21pub use artifact::{ArtifactInput, ArtifactOutput};
22pub use decision::{DEFAULT_DECISION_MODEL, DecisionConfig};
23pub use delay::DelayConfig;
24pub use escalation::{EscalationPolicy, NotificationTarget};
25pub use http::HttpConfig;
26pub use ironflow_store::entities::{ApprovalRequirement, Assignee};
29pub use shell::ShellConfig;
30pub use workflow::WorkflowStepConfig;
31
32pub use crate::expression::{Expression, ExpressionError};
33
34use ironflow_core::retry::RetryPolicy;
35use ironflow_store::entities::StepKind;
36use serde::{Deserialize, Serialize};
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "snake_case")]
54pub enum StepConfig {
55 Shell(ShellConfig),
57 Http(HttpConfig),
59 Agent(AgentStepConfig),
61 Workflow(WorkflowStepConfig),
63 Approval(ApprovalConfig),
65 Decision(DecisionConfig),
67 Delay(DelayConfig),
69}
70
71impl StepConfig {
72 pub fn allow_failure(&self) -> bool {
86 match self {
87 StepConfig::Shell(c) => c.allow_failure,
88 StepConfig::Http(c) => c.allow_failure,
89 StepConfig::Agent(c) => c.allow_failure,
90 StepConfig::Workflow(_)
91 | StepConfig::Approval(_)
92 | StepConfig::Decision(_)
93 | StepConfig::Delay(_) => false,
94 }
95 }
96
97 pub fn retry(&self) -> Option<&RetryPolicy> {
112 match self {
113 StepConfig::Shell(c) => c.retry.as_ref(),
114 StepConfig::Http(c) => c.retry.as_ref(),
115 StepConfig::Agent(c) => c.retry.as_ref(),
116 StepConfig::Workflow(c) => c.retry.as_ref(),
117 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
118 }
119 }
120
121 pub fn kind(&self) -> StepKind {
133 match self {
134 StepConfig::Shell(_) => StepKind::Shell,
135 StepConfig::Http(_) => StepKind::Http,
136 StepConfig::Agent(_) => StepKind::Agent,
137 StepConfig::Workflow(_) => StepKind::Workflow,
138 StepConfig::Approval(_) => StepKind::Approval,
139 StepConfig::Decision(_) => StepKind::Decision,
140 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
141 }
142 }
143}
144
145impl From<ShellConfig> for StepConfig {
146 fn from(c: ShellConfig) -> Self {
147 StepConfig::Shell(c)
148 }
149}
150
151impl From<HttpConfig> for StepConfig {
152 fn from(c: HttpConfig) -> Self {
153 StepConfig::Http(c)
154 }
155}
156
157impl From<AgentStepConfig> for StepConfig {
158 fn from(c: AgentStepConfig) -> Self {
159 StepConfig::Agent(c)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use ironflow_core::retry::RetryPolicy;
166
167 use super::*;
168
169 #[test]
170 fn retry_accessor_returns_policy_for_each_variant() {
171 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
172 assert_eq!(shell.retry().unwrap().max_retries(), 2);
173
174 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
175 assert_eq!(http.retry().unwrap().max_retries(), 3);
176
177 let workflow = StepConfig::Workflow(
178 WorkflowStepConfig::new("build", serde_json::json!({}))
179 .retry_policy(RetryPolicy::new(4)),
180 );
181 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
182
183 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
184 assert!(agent.retry().is_none());
185
186 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
187 assert!(approval.retry().is_none());
188 }
189
190 #[test]
191 fn serde_roundtrip() {
192 let configs = vec![
193 StepConfig::Shell(ShellConfig::new("echo test")),
194 StepConfig::Http(HttpConfig::get("http://example.com")),
195 StepConfig::Agent(AgentStepConfig::new("summarize")),
196 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
197 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
198 StepConfig::Delay(DelayConfig::from_secs(60)),
199 ];
200
201 for config in configs {
202 let json = serde_json::to_string(&config).expect("serialize");
203 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
204 let json2 = serde_json::to_string(&back).expect("serialize2");
205 assert_eq!(json, json2);
206 }
207 }
208
209 #[test]
210 fn approval_with_escalation_roundtrips() {
211 let config = StepConfig::Approval(
212 ApprovalConfig::new("Deploy to production?")
213 .with_deadline_secs(3600)
214 .assigned_to(Assignee::group("release-managers"))
215 .on_timeout(EscalationPolicy::Chain(vec![
216 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
217 url: "https://example.com/sla".to_string(),
218 }]),
219 EscalationPolicy::AutoReject,
220 ])),
221 );
222
223 let json = serde_json::to_string(&config).expect("serialize");
224 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
225 let json2 = serde_json::to_string(&back).expect("serialize2");
226 assert_eq!(json, json2);
227
228 let StepConfig::Approval(approval) = back else {
229 panic!("expected an approval config");
230 };
231 assert_eq!(approval.effective_deadline_secs(), Some(3600));
232 assert_eq!(
233 approval.assignee(),
234 Some(&Assignee::group("release-managers"))
235 );
236 assert_eq!(approval.effective_policy().len(), 2);
237 }
238}