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::{
20 AgentStep, AgentStepConfig, DEFAULT_RESUME_PROMPT, DEFAULT_UNTRUSTED_INSTRUCTION, Prompt, Tool,
21 ToolProfile,
22};
23pub use approval::ApprovalConfig;
24pub use approvers::Approvers;
25pub use artifact::{ArtifactInput, ArtifactOutput, ArtifactRef};
26pub use decision::{DEFAULT_DECISION_MODEL, DecisionConfig, NoAnswers};
27pub use delay::DelayConfig;
28pub use escalation::{EscalationPolicy, NotificationTarget};
29pub use http::HttpConfig;
30pub use human_input::{HUMAN_INPUT_SCHEMA_KEY, HumanInputConfig};
31pub use ironflow_store::entities::{ApprovalRequirement, Assignee};
34pub use shell::ShellConfig;
35pub use workflow::{WorkflowOptions, WorkflowStepConfig};
36
37use ironflow_core::retry::RetryPolicy;
38use ironflow_store::entities::StepKind;
39use serde::{Deserialize, Serialize};
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(tag = "type", rename_all = "snake_case")]
57#[allow(clippy::large_enum_variant)]
60pub enum StepConfig {
61 Shell(ShellConfig),
63 Http(HttpConfig),
65 Agent(AgentStepConfig),
67 Workflow(WorkflowStepConfig),
69 Approval(ApprovalConfig),
71 Decision(DecisionConfig),
73 Delay(DelayConfig),
75}
76
77impl StepConfig {
78 pub fn allow_failure(&self) -> bool {
92 match self {
93 StepConfig::Shell(c) => c.allow_failure,
94 StepConfig::Http(c) => c.allow_failure,
95 StepConfig::Agent(c) => c.allow_failure,
96 StepConfig::Workflow(_)
97 | StepConfig::Approval(_)
98 | StepConfig::Decision(_)
99 | StepConfig::Delay(_) => false,
100 }
101 }
102
103 pub fn declared_outputs(&self) -> &[ArtifactOutput] {
117 match self {
118 StepConfig::Shell(c) => &c.outputs,
119 _ => &[],
120 }
121 }
122
123 pub fn retry(&self) -> Option<&RetryPolicy> {
138 match self {
139 StepConfig::Shell(c) => c.retry.as_ref(),
140 StepConfig::Http(c) => c.retry.as_ref(),
141 StepConfig::Agent(c) => c.retry.as_ref(),
142 StepConfig::Workflow(c) => c.retry.as_ref(),
143 StepConfig::Approval(_) | StepConfig::Decision(_) | StepConfig::Delay(_) => None,
144 }
145 }
146
147 pub fn kind(&self) -> StepKind {
159 match self {
160 StepConfig::Shell(_) => StepKind::Shell,
161 StepConfig::Http(_) => StepKind::Http,
162 StepConfig::Agent(_) => StepKind::Agent,
163 StepConfig::Workflow(_) => StepKind::Workflow,
164 StepConfig::Approval(_) => StepKind::Approval,
165 StepConfig::Decision(_) => StepKind::Decision,
166 StepConfig::Delay(_) => StepKind::Custom("delay".to_string()),
167 }
168 }
169}
170
171impl From<ShellConfig> for StepConfig {
172 fn from(c: ShellConfig) -> Self {
173 StepConfig::Shell(c)
174 }
175}
176
177impl From<HttpConfig> for StepConfig {
178 fn from(c: HttpConfig) -> Self {
179 StepConfig::Http(c)
180 }
181}
182
183impl From<AgentStepConfig> for StepConfig {
184 fn from(c: AgentStepConfig) -> Self {
185 StepConfig::Agent(c)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use ironflow_core::retry::RetryPolicy;
192
193 use super::*;
194
195 #[test]
196 fn retry_accessor_returns_policy_for_each_variant() {
197 let shell = StepConfig::Shell(ShellConfig::new("echo").retry_policy(RetryPolicy::new(2)));
198 assert_eq!(shell.retry().unwrap().max_retries(), 2);
199
200 let http = StepConfig::Http(HttpConfig::get("http://x").retry_policy(RetryPolicy::new(3)));
201 assert_eq!(http.retry().unwrap().max_retries(), 3);
202
203 let workflow = StepConfig::Workflow(
204 WorkflowStepConfig::new("build", serde_json::json!({}))
205 .retry_policy(RetryPolicy::new(4)),
206 );
207 assert_eq!(workflow.retry().unwrap().max_retries(), 4);
208
209 let agent = StepConfig::Agent(AgentStepConfig::new("test"));
210 assert!(agent.retry().is_none());
211
212 let approval = StepConfig::Approval(ApprovalConfig::new("ok?"));
213 assert!(approval.retry().is_none());
214 }
215
216 #[test]
217 fn serde_roundtrip() {
218 let configs = vec![
219 StepConfig::Shell(ShellConfig::new("echo test")),
220 StepConfig::Http(HttpConfig::get("http://example.com")),
221 StepConfig::Agent(AgentStepConfig::new("summarize")),
222 StepConfig::Workflow(WorkflowStepConfig::new("build", serde_json::json!({}))),
223 StepConfig::Approval(ApprovalConfig::new("Deploy to production?")),
224 StepConfig::Delay(DelayConfig::from_secs(60)),
225 ];
226
227 for config in configs {
228 let json = serde_json::to_string(&config).expect("serialize");
229 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
230 let json2 = serde_json::to_string(&back).expect("serialize2");
231 assert_eq!(json, json2);
232 }
233 }
234
235 #[test]
236 fn approval_with_escalation_roundtrips() {
237 let config = StepConfig::Approval(
238 ApprovalConfig::new("Deploy to production?")
239 .with_deadline_secs(3600)
240 .assigned_to(Assignee::group("release-managers"))
241 .on_timeout(EscalationPolicy::Chain(vec![
242 EscalationPolicy::Notify(vec![NotificationTarget::Webhook {
243 url: "https://example.com/sla".to_string(),
244 }]),
245 EscalationPolicy::AutoReject,
246 ])),
247 );
248
249 let json = serde_json::to_string(&config).expect("serialize");
250 let back: StepConfig = serde_json::from_str(&json).expect("deserialize");
251 let json2 = serde_json::to_string(&back).expect("serialize2");
252 assert_eq!(json, json2);
253
254 let StepConfig::Approval(approval) = back else {
255 panic!("expected an approval config");
256 };
257 assert_eq!(approval.effective_deadline_secs(), Some(3600));
258 assert_eq!(
259 approval.assignee(),
260 Some(&Assignee::group("release-managers"))
261 );
262 assert_eq!(approval.effective_policy().len(), 2);
263 }
264}