Skip to main content

validation_engine/
guard.rs

1use crate::engine::ExternalRuleSource;
2use guard_translator::load_guard_sources_recursive;
3use std::fs;
4use std::path::Path;
5
6pub fn resolve_guard_config(rule_source_paths: &[String]) -> Result<Vec<ExternalRuleSource>, String> {
7    let mut entries = Vec::new();
8
9    for path in rule_source_paths {
10        let p = Path::new(path);
11        if p.is_dir() {
12            let sources = load_guard_sources_recursive(path)?;
13            for (file_path, file_content) in sources {
14                entries.push(ExternalRuleSource { name: file_path, content: file_content });
15            }
16        } else if p.is_file() {
17            let file_content =
18                fs::read_to_string(p).map_err(|e| format!("Failed to read guard file '{}': {}", path, e))?;
19            entries.push(ExternalRuleSource { name: path.clone(), content: file_content });
20        } else {
21            return Err(format!("Guard rule source not found: {}", path));
22        }
23    }
24
25    Ok(entries)
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use std::env;
32    use std::fs;
33
34    #[test]
35    fn resolve_guard_config_single_file() {
36        let dir = env::temp_dir().join("guard_test_single");
37        let _ = fs::remove_dir_all(&dir);
38        fs::create_dir_all(&dir).unwrap();
39        let file = dir.join("test.guard");
40        fs::write(&file, "rule example { true }").unwrap();
41
42        let result = resolve_guard_config(&[file.to_string_lossy().to_string()]);
43        let entries = result.expect("resolve_guard_config should succeed for single file");
44        assert_eq!(entries.len(), 1);
45        assert!(entries[0].content.contains("rule example"));
46        assert!(entries[0].name.contains("test.guard"));
47
48        let _ = fs::remove_dir_all(&dir);
49    }
50
51    #[test]
52    fn resolve_guard_config_directory_recursive() {
53        let dir = env::temp_dir().join("guard_test_dir");
54        let _ = fs::remove_dir_all(&dir);
55        let sub = dir.join("sub");
56        fs::create_dir_all(&sub).unwrap();
57        fs::write(dir.join("a.guard"), "rule a { true }").unwrap();
58        fs::write(sub.join("b.guard"), "rule b { true }").unwrap();
59
60        let result = resolve_guard_config(&[dir.to_string_lossy().to_string()]);
61        let entries = result.expect("resolve_guard_config should succeed for directory");
62        assert_eq!(entries.len(), 2);
63
64        let _ = fs::remove_dir_all(&dir);
65    }
66
67    #[test]
68    fn resolve_guard_config_nonexistent_path_returns_error() {
69        let result = resolve_guard_config(&["/nonexistent/path/to/guard.guard".into()]);
70        let err = result.unwrap_err();
71        assert!(err.contains("not found"), "error should mention 'not found', got: {err}");
72    }
73
74    #[test]
75    fn resolve_guard_config_empty_paths_returns_empty() {
76        let result = resolve_guard_config(&[]);
77        let entries = result.expect("empty paths should succeed");
78        assert_eq!(entries.len(), 0, "empty paths should return empty vec");
79    }
80
81    #[test]
82    fn resolve_guard_config_mixed_file_and_dir() {
83        let dir = env::temp_dir().join("guard_test_mixed");
84        let _ = fs::remove_dir_all(&dir);
85        let sub = dir.join("rules");
86        fs::create_dir_all(&sub).unwrap();
87        let standalone = dir.join("standalone.guard");
88        fs::write(&standalone, "rule standalone { true }").unwrap();
89        fs::write(sub.join("packed.guard"), "rule packed { true }").unwrap();
90
91        let result =
92            resolve_guard_config(&[standalone.to_string_lossy().to_string(), sub.to_string_lossy().to_string()]);
93        let entries = result.expect("mixed file and dir should succeed");
94        assert_eq!(entries.len(), 2);
95
96        let _ = fs::remove_dir_all(&dir);
97    }
98
99    #[test]
100    fn resolve_guard_config_preserves_file_content() {
101        let dir = env::temp_dir().join("guard_test_content");
102        let _ = fs::remove_dir_all(&dir);
103        fs::create_dir_all(&dir).unwrap();
104        let file = dir.join("content.guard");
105        let guard_source = "rule check_bucket {\n  AWS::S3::Bucket {\n    Properties.BucketName exists\n  }\n}";
106        fs::write(&file, guard_source).unwrap();
107
108        let entries = resolve_guard_config(&[file.to_string_lossy().to_string()]).unwrap();
109        assert_eq!(entries[0].content, guard_source);
110
111        let _ = fs::remove_dir_all(&dir);
112    }
113}