1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ChainStep {
6 pub allow: String,
8 pub resource: Option<String>,
10 pub profile: Option<String>,
12 pub ttl: Option<String>,
14 pub command: Vec<String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct StepResult {
21 pub step_index: usize,
22 pub allow: String,
23 pub exit_code: i32,
24 pub stdout: String,
25 pub stderr: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ChainResult {
31 pub steps: Vec<StepResult>,
32 pub total_steps: usize,
33 pub completed_steps: usize,
34 pub success: bool,
35}
36
37impl ChainResult {
38 pub fn new(total: usize) -> Self {
39 Self {
40 steps: Vec::new(),
41 total_steps: total,
42 completed_steps: 0,
43 success: true,
44 }
45 }
46
47 pub fn add_step(&mut self, result: StepResult) {
48 if result.exit_code != 0 {
49 self.success = false;
50 }
51 self.completed_steps += 1;
52 self.steps.push(result);
53 }
54}
55
56pub fn parse_step_spec(spec: &str) -> ChainStep {
59 ChainStep {
60 allow: spec.to_string(),
61 resource: None,
62 profile: None,
63 ttl: None,
64 command: Vec::new(),
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_parse_step_spec() {
74 let step = parse_step_spec("s3:GetObject,s3:ListBucket");
75 assert_eq!(step.allow, "s3:GetObject,s3:ListBucket");
76 assert!(step.resource.is_none());
77 assert!(step.command.is_empty());
78 }
79
80 #[test]
81 fn test_chain_result_success() {
82 let mut result = ChainResult::new(2);
83 result.add_step(StepResult {
84 step_index: 0,
85 allow: "s3:GetObject".to_string(),
86 exit_code: 0,
87 stdout: "ok".to_string(),
88 stderr: String::new(),
89 });
90 result.add_step(StepResult {
91 step_index: 1,
92 allow: "lambda:InvokeFunction".to_string(),
93 exit_code: 0,
94 stdout: "ok".to_string(),
95 stderr: String::new(),
96 });
97 assert!(result.success);
98 assert_eq!(result.completed_steps, 2);
99 }
100
101 #[test]
102 fn test_chain_result_failure_stops() {
103 let mut result = ChainResult::new(3);
104 result.add_step(StepResult {
105 step_index: 0,
106 allow: "s3:GetObject".to_string(),
107 exit_code: 0,
108 stdout: "ok".to_string(),
109 stderr: String::new(),
110 });
111 result.add_step(StepResult {
112 step_index: 1,
113 allow: "lambda:InvokeFunction".to_string(),
114 exit_code: 1,
115 stdout: String::new(),
116 stderr: "access denied".to_string(),
117 });
118 assert!(!result.success);
119 assert_eq!(result.completed_steps, 2);
120 }
121
122 #[test]
123 fn test_chain_result_serializable() {
124 let result = ChainResult::new(1);
125 let json = serde_json::to_string(&result).unwrap();
126 assert!(json.contains("\"total_steps\":1"));
127 }
128}