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