ironflow_engine/config/
mod.rs1mod agent;
8mod approval;
9mod approvers;
10mod artifact;
11mod decision;
12pub mod delay;
13mod escalation;
14mod http;
15mod human_input;
16mod shell;
17mod workflow;
18
19pub use agent::{AgentStep, AgentStepConfig, Tool, ToolProfile};
20pub use approval::ApprovalConfig;
21pub use approvers::Approvers;
22pub use artifact::{ArtifactInput, ArtifactOutput, ArtifactRef};
23pub use decision::{DEFAULT_DECISION_MODEL, DecisionConfig, NoAnswers};
24pub use delay::DelayConfig;
25pub use escalation::{EscalationPolicy, NotificationTarget};
26pub use http::HttpConfig;
27pub use human_input::{HUMAN_INPUT_SCHEMA_KEY, HumanInputConfig};
28pub use ironflow_store::entities::{ApprovalRequirement, Assignee};
31pub use shell::ShellConfig;
32pub use workflow::WorkflowStepConfig;
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 declared_outputs(&self) -> &[ArtifactOutput] {
111 match self {
112 StepConfig::Shell(c) => &c.outputs,
113 _ => &[],
114 }
115 }
116
117 pub fn retry(&self) -> Option<&RetryPolicy> {
132 match self {
133 StepConfig::Shell(c) => c.retry.as_ref(),
134 StepConfig::Http(c) => c.retry.as_ref(),
135 StepConfig::Agent(c) => c.retry.as_ref(),
136 StepConfig::Workflow(c) => c.retry.as_ref(),
137 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
138 }
139 }
140
141 pub fn kind(&self) -> StepKind {
153 match self {
154 StepConfig::Shell(_) => StepKind::Shell,
155 StepConfig::Http(_) => StepKind::Http,
156 StepConfig::Agent(_) => StepKind::Agent,
157 StepConfig::Workflow(_) => StepKind::Workflow,
158 StepConfig::Approval(_) => StepKind::Approval,
159 StepConfig::Decision(_) => StepKind::Decision,
160 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
161 }
162 }
163}
164
165impl From<ShellConfig> for StepConfig {
166 fn from(c: ShellConfig) -> Self {
167 StepConfig::Shell(c)
168 }
169}
170
171impl From<HttpConfig> for StepConfig {
172 fn from(c: HttpConfig) -> Self {
173 StepConfig::Http(c)
174 }
175}
176
177impl From<AgentStepConfig> for StepConfig {
178 fn from(c: AgentStepConfig) -> Self {
179 StepConfig::Agent(c)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use ironflow_core::retry::RetryPolicy;
186
187 use super::*;
188
189 #[test]
190 fn retry_accessor_returns_policy_for_each_variant() {
191 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
192 assert_eq!(shell.retry().unwrap().max_retries(), 2);
193
194 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
195 assert_eq!(http.retry().unwrap().max_retries(), 3);
196
197 let workflow = StepConfig::Workflow(
198 WorkflowStepConfig::new("build", serde_json::json!({}))
199 .retry_policy(RetryPolicy::new(4)),
200 );
201 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
202
203 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
204 assert!(agent.retry().is_none());
205
206 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
207 assert!(approval.retry().is_none());
208 }
209
210 #[test]
211 fn serde_roundtrip() {
212 let configs = vec![
213 StepConfig::Shell(ShellConfig::new("echo test")),
214 StepConfig::Http(HttpConfig::get("http://example.com")),
215 StepConfig::Agent(AgentStepConfig::new("summarize")),
216 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
217 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
218 StepConfig::Delay(DelayConfig::from_secs(60)),
219 ];
220
221 for config in configs {
222 let json = serde_json::to_string(&config).expect("serialize");
223 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
224 let json2 = serde_json::to_string(&back).expect("serialize2");
225 assert_eq!(json, json2);
226 }
227 }
228
229 #[test]
230 fn approval_with_escalation_roundtrips() {
231 let config = StepConfig::Approval(
232 ApprovalConfig::new("Deploy to production?")
233 .with_deadline_secs(3600)
234 .assigned_to(Assignee::group("release-managers"))
235 .on_timeout(EscalationPolicy::Chain(vec![
236 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
237 url: "https://example.com/sla".to_string(),
238 }]),
239 EscalationPolicy::AutoReject,
240 ])),
241 );
242
243 let json = serde_json::to_string(&config).expect("serialize");
244 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
245 let json2 = serde_json::to_string(&back).expect("serialize2");
246 assert_eq!(json, json2);
247
248 let StepConfig::Approval(approval) = back else {
249 panic!("expected an approval config");
250 };
251 assert_eq!(approval.effective_deadline_secs(), Some(3600));
252 assert_eq!(
253 approval.assignee(),
254 Some(&Assignee::group("release-managers"))
255 );
256 assert_eq!(approval.effective_policy().len(), 2);
257 }
258}