use std::path::Path;
use provable_contracts::schema::{Contract, parse_contract};
pub fn collect_contracts(dir: &Path, out: &mut Vec<(String, Contract)>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_contracts(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("yaml") {
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
if stem == "binding" || stem.contains("playbook") {
continue;
}
if let Ok(c) = parse_contract(&path) {
out.push((stem, c));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collect_from_missing_dir_is_empty() {
let mut out = Vec::new();
collect_contracts(Path::new("/nonexistent/path/to/contracts"), &mut out);
assert!(out.is_empty());
}
#[test]
fn collect_from_real_contracts_dir() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts");
if !dir.exists() {
return; }
let mut out = Vec::new();
collect_contracts(&dir, &mut out);
assert!(
out.len() > 100,
"expected > 100 contracts across the tree, got {}",
out.len()
);
for (stem, _) in &out {
assert!(!stem.is_empty(), "empty stem in collected contracts");
assert!(
!stem.contains("playbook"),
"playbook sidecar was not skipped: {stem}"
);
assert_ne!(stem, "binding", "binding sidecar was not skipped");
}
}
#[test]
fn collect_skips_binding_yaml() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("binding.yaml"),
"crates: []\nbindings: []\n",
)
.unwrap();
std::fs::write(
tmp.path().join("real-contract-v1.yaml"),
"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",
)
.unwrap();
let mut out = Vec::new();
collect_contracts(tmp.path(), &mut out);
let stems: Vec<_> = out.iter().map(|(s, _)| s.as_str()).collect();
assert!(!stems.contains(&"binding"), "binding should be skipped");
}
#[test]
fn collect_recurses_into_subdirs() {
let tmp = tempfile::tempdir().unwrap();
let sub = tmp.path().join("sub");
std::fs::create_dir_all(&sub).unwrap();
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";
std::fs::write(tmp.path().join("top-v1.yaml"), minimal).unwrap();
std::fs::write(sub.join("nested-v1.yaml"), minimal).unwrap();
let mut out = Vec::new();
collect_contracts(tmp.path(), &mut out);
let stems: Vec<_> = out.iter().map(|(s, _)| s.clone()).collect();
assert!(stems.contains(&"top-v1".to_string()));
assert!(stems.contains(&"nested-v1".to_string()));
}
}