1use crate::error::{WorkflowError, WorkflowResult};
4use chrono::{DateTime, Duration, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
10pub enum ApprovalDecision {
11 #[serde(rename = "approved")]
13 Approved,
14 #[serde(rename = "rejected")]
16 Rejected,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ApprovalRequest {
22 pub id: String,
24 pub step_id: String,
26 pub message: String,
28 pub created_at: DateTime<Utc>,
30 pub timeout_ms: u64,
32 pub approved: bool,
34 pub decision: Option<ApprovalDecision>,
36 pub approved_at: Option<DateTime<Utc>>,
38 pub comments: Option<String>,
40}
41
42impl ApprovalRequest {
43 pub fn new(step_id: String, message: String, timeout_ms: u64) -> Self {
45 ApprovalRequest {
46 id: uuid::Uuid::new_v4().to_string(),
47 step_id,
48 message,
49 created_at: Utc::now(),
50 timeout_ms,
51 approved: false,
52 decision: None,
53 approved_at: None,
54 comments: None,
55 }
56 }
57
58 pub fn is_timed_out(&self) -> bool {
60 let timeout_duration = Duration::milliseconds(self.timeout_ms as i64);
61 Utc::now() > self.created_at + timeout_duration
62 }
63
64 pub fn is_pending(&self) -> bool {
66 !self.approved && !self.is_timed_out()
67 }
68
69 pub fn approve(&mut self, comments: Option<String>) {
71 self.approved = true;
72 self.decision = Some(ApprovalDecision::Approved);
73 self.approved_at = Some(Utc::now());
74 self.comments = comments;
75 }
76
77 pub fn reject(&mut self, comments: Option<String>) {
79 self.approved = true;
80 self.decision = Some(ApprovalDecision::Rejected);
81 self.approved_at = Some(Utc::now());
82 self.comments = comments;
83 }
84}
85
86pub struct ApprovalGate {
88 requests: HashMap<String, ApprovalRequest>,
90}
91
92impl Default for ApprovalGate {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl ApprovalGate {
99 pub fn new() -> Self {
101 ApprovalGate {
102 requests: HashMap::new(),
103 }
104 }
105
106 pub fn request_approval(
111 &mut self,
112 step_id: String,
113 message: String,
114 timeout_ms: u64,
115 ) -> WorkflowResult<String> {
116 let request = ApprovalRequest::new(step_id, message, timeout_ms);
117 let request_id = request.id.clone();
118 self.requests.insert(request_id.clone(), request);
119 Ok(request_id)
120 }
121
122 pub fn approve(&mut self, request_id: &str, comments: Option<String>) -> WorkflowResult<()> {
127 let request = self.requests.get_mut(request_id).ok_or_else(|| {
128 WorkflowError::NotFound(format!("Approval request not found: {}", request_id))
129 })?;
130
131 if request.approved {
132 return Err(WorkflowError::Invalid(format!(
133 "Approval request already decided: {}",
134 request_id
135 )));
136 }
137
138 if request.is_timed_out() {
139 return Err(WorkflowError::ApprovalTimeout);
140 }
141
142 request.approve(comments);
143 Ok(())
144 }
145
146 pub fn reject(&mut self, request_id: &str, comments: Option<String>) -> WorkflowResult<()> {
151 let request = self.requests.get_mut(request_id).ok_or_else(|| {
152 WorkflowError::NotFound(format!("Approval request not found: {}", request_id))
153 })?;
154
155 if request.approved {
156 return Err(WorkflowError::Invalid(format!(
157 "Approval request already decided: {}",
158 request_id
159 )));
160 }
161
162 if request.is_timed_out() {
163 return Err(WorkflowError::ApprovalTimeout);
164 }
165
166 request.reject(comments);
167 Ok(())
168 }
169
170 pub fn get_request_status(&self, request_id: &str) -> WorkflowResult<ApprovalRequest> {
172 self.requests.get(request_id).cloned().ok_or_else(|| {
173 WorkflowError::NotFound(format!("Approval request not found: {}", request_id))
174 })
175 }
176
177 pub fn is_approved(&self, request_id: &str) -> WorkflowResult<bool> {
182 let request = self.get_request_status(request_id)?;
183
184 if request.is_timed_out() {
185 return Err(WorkflowError::ApprovalTimeout);
186 }
187
188 if !request.approved {
189 return Ok(false);
190 }
191
192 Ok(request.decision == Some(ApprovalDecision::Approved))
193 }
194
195 pub fn is_rejected(&self, request_id: &str) -> WorkflowResult<bool> {
200 let request = self.get_request_status(request_id)?;
201
202 if request.is_timed_out() {
203 return Err(WorkflowError::ApprovalTimeout);
204 }
205
206 if !request.approved {
207 return Ok(false);
208 }
209
210 Ok(request.decision == Some(ApprovalDecision::Rejected))
211 }
212
213 pub fn is_pending(&self, request_id: &str) -> WorkflowResult<bool> {
215 let request = self.get_request_status(request_id)?;
216 Ok(request.is_pending())
217 }
218
219 pub fn get_pending_requests(&self) -> Vec<ApprovalRequest> {
221 self.requests
222 .values()
223 .filter(|r| r.is_pending())
224 .cloned()
225 .collect()
226 }
227
228 pub fn get_step_requests(&self, step_id: &str) -> Vec<ApprovalRequest> {
230 self.requests
231 .values()
232 .filter(|r| r.step_id == step_id)
233 .cloned()
234 .collect()
235 }
236
237 #[cfg(test)]
239 pub fn clear(&mut self) {
240 self.requests.clear();
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_create_approval_request() {
250 let request = ApprovalRequest::new(
251 "step1".to_string(),
252 "Please approve this step".to_string(),
253 5000,
254 );
255
256 assert_eq!(request.step_id, "step1");
257 assert_eq!(request.message, "Please approve this step");
258 assert_eq!(request.timeout_ms, 5000);
259 assert!(!request.approved);
260 assert!(request.is_pending());
261 }
262
263 #[test]
264 fn test_approval_request_timeout() {
265 let request = ApprovalRequest::new(
266 "step1".to_string(),
267 "Please approve this step".to_string(),
268 1, );
270
271 std::thread::sleep(std::time::Duration::from_millis(10));
273
274 assert!(request.is_timed_out());
275 assert!(!request.is_pending());
276 }
277
278 #[test]
279 fn test_approve_request() {
280 let mut request = ApprovalRequest::new(
281 "step1".to_string(),
282 "Please approve this step".to_string(),
283 5000,
284 );
285
286 request.approve(Some("Looks good".to_string()));
287
288 assert!(request.approved);
289 assert_eq!(request.decision, Some(ApprovalDecision::Approved));
290 assert_eq!(request.comments, Some("Looks good".to_string()));
291 assert!(!request.is_pending());
292 }
293
294 #[test]
295 fn test_reject_request() {
296 let mut request = ApprovalRequest::new(
297 "step1".to_string(),
298 "Please approve this step".to_string(),
299 5000,
300 );
301
302 request.reject(Some("Needs changes".to_string()));
303
304 assert!(request.approved);
305 assert_eq!(request.decision, Some(ApprovalDecision::Rejected));
306 assert_eq!(request.comments, Some("Needs changes".to_string()));
307 assert!(!request.is_pending());
308 }
309
310 #[test]
311 fn test_approval_gate_request_approval() {
312 let mut gate = ApprovalGate::new();
313
314 let request_id = gate
315 .request_approval("step1".to_string(), "Please approve".to_string(), 5000)
316 .unwrap();
317
318 assert!(!request_id.is_empty());
319 assert_eq!(gate.get_pending_requests().len(), 1);
320 }
321
322 #[test]
323 fn test_approval_gate_approve() {
324 let mut gate = ApprovalGate::new();
325
326 let request_id = gate
327 .request_approval("step1".to_string(), "Please approve".to_string(), 5000)
328 .unwrap();
329
330 gate.approve(&request_id, Some("Approved".to_string()))
331 .unwrap();
332
333 assert!(gate.is_approved(&request_id).unwrap());
334 assert!(!gate.is_rejected(&request_id).unwrap());
335 assert!(!gate.is_pending(&request_id).unwrap());
336 }
337
338 #[test]
339 fn test_approval_gate_reject() {
340 let mut gate = ApprovalGate::new();
341
342 let request_id = gate
343 .request_approval("step1".to_string(), "Please approve".to_string(), 5000)
344 .unwrap();
345
346 gate.reject(&request_id, Some("Rejected".to_string()))
347 .unwrap();
348
349 assert!(!gate.is_approved(&request_id).unwrap());
350 assert!(gate.is_rejected(&request_id).unwrap());
351 assert!(!gate.is_pending(&request_id).unwrap());
352 }
353
354 #[test]
355 fn test_approval_gate_get_step_requests() {
356 let mut gate = ApprovalGate::new();
357
358 let _req1 = gate
359 .request_approval("step1".to_string(), "Please approve".to_string(), 5000)
360 .unwrap();
361
362 let _req2 = gate
363 .request_approval(
364 "step1".to_string(),
365 "Please approve again".to_string(),
366 5000,
367 )
368 .unwrap();
369
370 let _req3 = gate
371 .request_approval("step2".to_string(), "Please approve".to_string(), 5000)
372 .unwrap();
373
374 let step1_requests = gate.get_step_requests("step1");
375 assert_eq!(step1_requests.len(), 2);
376
377 let step2_requests = gate.get_step_requests("step2");
378 assert_eq!(step2_requests.len(), 1);
379 }
380
381 #[test]
382 fn test_approval_gate_cannot_approve_twice() {
383 let mut gate = ApprovalGate::new();
384
385 let request_id = gate
386 .request_approval("step1".to_string(), "Please approve".to_string(), 5000)
387 .unwrap();
388
389 gate.approve(&request_id, None).unwrap();
390
391 let result = gate.approve(&request_id, None);
392 assert!(result.is_err());
393 }
394
395 #[test]
396 fn test_approval_gate_cannot_approve_after_timeout() {
397 let mut gate = ApprovalGate::new();
398
399 let request_id = gate
400 .request_approval(
401 "step1".to_string(),
402 "Please approve".to_string(),
403 1, )
405 .unwrap();
406
407 std::thread::sleep(std::time::Duration::from_millis(10));
409
410 let result = gate.approve(&request_id, None);
411 assert!(result.is_err());
412 }
413}