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")]
54#[allow(clippy::large_enum_variant)]
57pub enum StepConfig {
58 Shell(ShellConfig),
60 Http(HttpConfig),
62 Agent(AgentStepConfig),
64 Workflow(WorkflowStepConfig),
66 Approval(ApprovalConfig),
68 Decision(DecisionConfig),
70 Delay(DelayConfig),
72}
73
74impl StepConfig {
75 pub fn allow_failure(&self) -> bool {
89 match self {
90 StepConfig::Shell(c) => c.allow_failure,
91 StepConfig::Http(c) => c.allow_failure,
92 StepConfig::Agent(c) => c.allow_failure,
93 StepConfig::Workflow(_)
94 | StepConfig::Approval(_)
95 | StepConfig::Decision(_)
96 | StepConfig::Delay(_) => false,
97 }
98 }
99
100 pub fn declared_outputs(&self) -> &[ArtifactOutput] {
114 match self {
115 StepConfig::Shell(c) => &c.outputs,
116 _ => &[],
117 }
118 }
119
120 pub fn retry(&self) -> Option<&RetryPolicy> {
135 match self {
136 StepConfig::Shell(c) => c.retry.as_ref(),
137 StepConfig::Http(c) => c.retry.as_ref(),
138 StepConfig::Agent(c) => c.retry.as_ref(),
139 StepConfig::Workflow(c) => c.retry.as_ref(),
140 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
141 }
142 }
143
144 pub fn kind(&self) -> StepKind {
156 match self {
157 StepConfig::Shell(_) => StepKind::Shell,
158 StepConfig::Http(_) => StepKind::Http,
159 StepConfig::Agent(_) => StepKind::Agent,
160 StepConfig::Workflow(_) => StepKind::Workflow,
161 StepConfig::Approval(_) => StepKind::Approval,
162 StepConfig::Decision(_) => StepKind::Decision,
163 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
164 }
165 }
166}
167
168impl From<ShellConfig> for StepConfig {
169 fn from(c: ShellConfig) -> Self {
170 StepConfig::Shell(c)
171 }
172}
173
174impl From<HttpConfig> for StepConfig {
175 fn from(c: HttpConfig) -> Self {
176 StepConfig::Http(c)
177 }
178}
179
180impl From<AgentStepConfig> for StepConfig {
181 fn from(c: AgentStepConfig) -> Self {
182 StepConfig::Agent(c)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use ironflow_core::retry::RetryPolicy;
189
190 use super::*;
191
192 #[test]
193 fn retry_accessor_returns_policy_for_each_variant() {
194 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
195 assert_eq!(shell.retry().unwrap().max_retries(), 2);
196
197 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
198 assert_eq!(http.retry().unwrap().max_retries(), 3);
199
200 let workflow = StepConfig::Workflow(
201 WorkflowStepConfig::new("build", serde_json::json!({}))
202 .retry_policy(RetryPolicy::new(4)),
203 );
204 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
205
206 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
207 assert!(agent.retry().is_none());
208
209 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
210 assert!(approval.retry().is_none());
211 }
212
213 #[test]
214 fn serde_roundtrip() {
215 let configs = vec![
216 StepConfig::Shell(ShellConfig::new("echo test")),
217 StepConfig::Http(HttpConfig::get("http://example.com")),
218 StepConfig::Agent(AgentStepConfig::new("summarize")),
219 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
220 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
221 StepConfig::Delay(DelayConfig::from_secs(60)),
222 ];
223
224 for config in configs {
225 let json = serde_json::to_string(&config).expect("serialize");
226 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
227 let json2 = serde_json::to_string(&back).expect("serialize2");
228 assert_eq!(json, json2);
229 }
230 }
231
232 #[test]
233 fn approval_with_escalation_roundtrips() {
234 let config = StepConfig::Approval(
235 ApprovalConfig::new("Deploy to production?")
236 .with_deadline_secs(3600)
237 .assigned_to(Assignee::group("release-managers"))
238 .on_timeout(EscalationPolicy::Chain(vec![
239 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
240 url: "https://example.com/sla".to_string(),
241 }]),
242 EscalationPolicy::AutoReject,
243 ])),
244 );
245
246 let json = serde_json::to_string(&config).expect("serialize");
247 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
248 let json2 = serde_json::to_string(&back).expect("serialize2");
249 assert_eq!(json, json2);
250
251 let StepConfig::Approval(approval) = back else {
252 panic!("expected an approval config");
253 };
254 assert_eq!(approval.effective_deadline_secs(), Some(3600));
255 assert_eq!(
256 approval.assignee(),
257 Some(&Assignee::group("release-managers"))
258 );
259 assert_eq!(approval.effective_policy().len(), 2);
260 }
261}