aprender_contracts_cli/
contract_walk.rs1use std::path::Path;
10
11use provable_contracts::schema::{parse_contract, Contract};
12
13pub fn collect_contracts(dir: &Path, out: &mut Vec<(String, Contract)>) {
23 let Ok(entries) = std::fs::read_dir(dir) else {
24 return;
25 };
26 for entry in entries.flatten() {
27 let path = entry.path();
28 if path.is_dir() {
29 collect_contracts(&path, out);
30 } else if path.extension().and_then(|e| e.to_str()) == Some("yaml") {
31 let stem = path
32 .file_stem()
33 .and_then(|s| s.to_str())
34 .unwrap_or("unknown")
35 .to_string();
36 if stem == "binding" || stem.contains("playbook") {
37 continue;
38 }
39 if let Ok(c) = parse_contract(&path) {
40 out.push((stem, c));
41 }
42 }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn collect_from_missing_dir_is_empty() {
52 let mut out = Vec::new();
53 collect_contracts(Path::new("/nonexistent/path/to/contracts"), &mut out);
54 assert!(out.is_empty());
55 }
56
57 #[test]
58 fn collect_from_real_contracts_dir() {
59 let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts");
60 if !dir.exists() {
61 return; }
63 let mut out = Vec::new();
64 collect_contracts(&dir, &mut out);
65 assert!(
67 out.len() > 100,
68 "expected > 100 contracts across the tree, got {}",
69 out.len()
70 );
71 for (stem, _) in &out {
73 assert!(!stem.is_empty(), "empty stem in collected contracts");
74 assert!(
75 !stem.contains("playbook"),
76 "playbook sidecar was not skipped: {stem}"
77 );
78 assert_ne!(stem, "binding", "binding sidecar was not skipped");
79 }
80 }
81
82 #[test]
83 fn collect_skips_binding_yaml() {
84 let tmp = tempfile::tempdir().unwrap();
85 std::fs::write(
86 tmp.path().join("binding.yaml"),
87 "crates: []\nbindings: []\n",
88 )
89 .unwrap();
90 std::fs::write(
91 tmp.path().join("real-contract-v1.yaml"),
92 "metadata:\n version: \"1.0.0\"\n description: \"t\"\n references: [\"x\"]\nequations:\n eq1:\n formula: \"f(x)=x\"\nproof_obligations: []\nfalsification_tests: []\nkani_harnesses: []\n",
93 )
94 .unwrap();
95
96 let mut out = Vec::new();
97 collect_contracts(tmp.path(), &mut out);
98
99 let stems: Vec<_> = out.iter().map(|(s, _)| s.as_str()).collect();
100 assert!(!stems.contains(&"binding"), "binding should be skipped");
101 }
102
103 #[test]
104 fn collect_recurses_into_subdirs() {
105 let tmp = tempfile::tempdir().unwrap();
106 let sub = tmp.path().join("sub");
107 std::fs::create_dir_all(&sub).unwrap();
108 let minimal = "metadata:\n version: \"1.0.0\"\n description: \"t\"\n references: [\"x\"]\nequations:\n eq1:\n formula: \"f(x)=x\"\nproof_obligations: []\nfalsification_tests: []\nkani_harnesses: []\n";
109 std::fs::write(tmp.path().join("top-v1.yaml"), minimal).unwrap();
110 std::fs::write(sub.join("nested-v1.yaml"), minimal).unwrap();
111
112 let mut out = Vec::new();
113 collect_contracts(tmp.path(), &mut out);
114
115 let stems: Vec<_> = out.iter().map(|(s, _)| s.clone()).collect();
116 assert!(stems.contains(&"top-v1".to_string()));
117 assert!(stems.contains(&"nested-v1".to_string()));
118 }
119}