ironflow_engine/config/approval.rs
1//! [`ApprovalConfig`] -- configuration for human approval gates.
2
3use serde::{Deserialize, Serialize};
4
5/// Configuration for a human approval step.
6///
7/// When the workflow reaches an approval step, the run transitions to
8/// `AwaitingApproval` and waits for a human to approve or reject via
9/// the API.
10///
11/// # Examples
12///
13/// ```
14/// use ironflow_engine::config::ApprovalConfig;
15///
16/// let config = ApprovalConfig::new("Deploy to production?");
17/// assert_eq!(config.message(), "Deploy to production?");
18/// assert!(config.timeout_seconds().is_none());
19/// ```
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ApprovalConfig {
22 message: String,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 timeout_seconds: Option<u64>,
25}
26
27impl ApprovalConfig {
28 /// Create a new approval config with the given message.
29 ///
30 /// # Examples
31 ///
32 /// ```
33 /// use ironflow_engine::config::ApprovalConfig;
34 ///
35 /// let config = ApprovalConfig::new("Approve this deployment?");
36 /// assert_eq!(config.message(), "Approve this deployment?");
37 /// ```
38 pub fn new(message: &str) -> Self {
39 Self {
40 message: message.to_string(),
41 timeout_seconds: None,
42 }
43 }
44
45 /// Set an auto-reject timeout in seconds.
46 ///
47 /// If no approval or rejection is received within this duration,
48 /// the run is automatically rejected (marked as Failed).
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// use ironflow_engine::config::ApprovalConfig;
54 ///
55 /// let config = ApprovalConfig::new("Approve?")
56 /// .with_timeout_seconds(3600);
57 /// assert_eq!(config.timeout_seconds(), Some(3600));
58 /// ```
59 pub fn with_timeout_seconds(mut self, seconds: u64) -> Self {
60 self.timeout_seconds = Some(seconds);
61 self
62 }
63
64 /// The approval message displayed to reviewers.
65 pub fn message(&self) -> &str {
66 &self.message
67 }
68
69 /// Optional auto-reject timeout in seconds.
70 pub fn timeout_seconds(&self) -> Option<u64> {
71 self.timeout_seconds
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn new_sets_message() {
81 let config = ApprovalConfig::new("Deploy?");
82 assert_eq!(config.message(), "Deploy?");
83 assert!(config.timeout_seconds().is_none());
84 }
85
86 #[test]
87 fn with_timeout() {
88 let config = ApprovalConfig::new("Approve?").with_timeout_seconds(7200);
89 assert_eq!(config.timeout_seconds(), Some(7200));
90 }
91
92 #[test]
93 fn serde_roundtrip() {
94 let config = ApprovalConfig::new("Deploy to prod?").with_timeout_seconds(3600);
95
96 let json = serde_json::to_string(&config).expect("serialize");
97 let back: ApprovalConfig = serde_json::from_str(&json).expect("deserialize");
98
99 assert_eq!(back.message(), config.message());
100 assert_eq!(back.timeout_seconds(), config.timeout_seconds());
101 }
102
103 #[test]
104 fn serde_minimal() {
105 let config = ApprovalConfig::new("Approve?");
106 let json = serde_json::to_string(&config).expect("serialize");
107 assert!(!json.contains("timeout_seconds"));
108 }
109}