1use std::path::Path;
6use toml::Value as T;
7
8pub const DIR: &str = "crates/gym/catalog/phase-a";
10
11pub fn read_text(path: &Path) -> Result<String, String> {
14 if !path.is_dir() {
15 return std::fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()));
16 }
17 let mut files: Vec<_> = std::fs::read_dir(path)
18 .map_err(|e| format!("{}: {e}", path.display()))?
19 .flatten()
20 .map(|e| e.path())
21 .filter(|p| p.extension().is_some_and(|x| x == "toml"))
22 .collect();
23 files.sort();
24 if files.is_empty() {
25 return Err(format!("{}: no .toml files", path.display()));
26 }
27 let mut text = String::new();
28 for f in files {
29 text.push_str(&std::fs::read_to_string(&f).map_err(|e| format!("{}: {e}", f.display()))?);
30 text.push('\n');
31 }
32 Ok(text)
33}
34
35pub fn entries(path: &Path) -> Result<Vec<T>, String> {
37 let text = read_text(path)?;
38 let doc: T = toml::from_str(&text).map_err(|e| format!("{}: {e}", path.display()))?;
39 let faults = doc.get("fault").and_then(T::as_array).cloned().unwrap_or_default();
40 let mut seen = std::collections::BTreeSet::new();
41 for e in &faults {
42 let id = e.get("id").and_then(T::as_str).ok_or_else(|| format!("{}: an entry has no id", path.display()))?;
43 if !seen.insert(id.to_string()) {
44 return Err(format!("{}: id {id} is used twice", path.display()));
45 }
46 }
47 Ok(faults)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
57 fn the_catalog_is_a_directory_of_unique_ids() {
58 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("catalog/phase-a");
59 let all = entries(&dir).unwrap();
60 assert!(all.len() > 100);
61 let tmp = std::env::temp_dir().join(format!("planarian-catalog-test-{}", std::process::id()));
62 std::fs::create_dir_all(&tmp).unwrap();
63 std::fs::write(tmp.join("a.toml"), "[[fault]]\nid = \"x\"\n").unwrap();
64 std::fs::write(tmp.join("b.toml"), "[[fault]]\nid = \"x\"\n").unwrap();
65 assert!(entries(&tmp).unwrap_err().contains("used twice"));
66 std::fs::write(tmp.join("b.toml"), "[[fault]]\nid = \"y\"\n").unwrap();
67 let ids: Vec<_> = entries(&tmp).unwrap().iter().map(|e| e["id"].as_str().unwrap().to_string()).collect();
68 let _ = std::fs::remove_dir_all(&tmp);
69 assert_eq!(ids, ["x", "y"], "filename order");
70 }
71}