validation_engine/
guard.rs1use 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}