use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
fn mappings_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../mappings")
}
fn baseline_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/known_enum_map_collisions.txt")
}
fn collisions() -> BTreeSet<String> {
let root = mappings_root();
let mut found = BTreeSet::new();
for entry in walkdir(&root) {
if entry.extension().and_then(|e| e.to_str()) != Some("toml") {
continue;
}
let Ok(text) = std::fs::read_to_string(&entry) else {
continue;
};
let Ok(doc) = toml::from_str::<toml::Value>(&text) else {
continue;
};
let Some(fields) = doc.get("fields").and_then(|f| f.as_table()) else {
continue;
};
for (field_path, mapping) in fields {
let Some(m) = mapping.as_table() else {
continue;
};
let Some(enum_map) = m.get("enum_map").and_then(|e| e.as_table()) else {
continue;
};
let values: Vec<&str> = enum_map.values().filter_map(|v| v.as_str()).collect();
if values.iter().collect::<BTreeSet<_>>().len() == values.len() {
continue; }
if let Some(also) = m.get("also_enum_map").and_then(|e| e.as_table()) {
let joint: Vec<(Option<&str>, Option<&str>)> = enum_map
.iter()
.map(|(code, v)| (v.as_str(), also.get(code).and_then(|a| a.as_str())))
.collect();
if joint.iter().collect::<BTreeSet<_>>().len() == joint.len() {
continue;
}
}
let rel = entry.strip_prefix(&root).unwrap_or(&entry).display();
let target = m.get("target").and_then(|t| t.as_str()).unwrap_or("?");
found.insert(format!("{rel} {field_path} {target}"));
}
}
found
}
fn walkdir(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(d) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&d) else {
continue;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
} else {
out.push(p);
}
}
}
out
}
#[test]
fn every_enum_map_is_invertible_or_known() {
let found = collisions();
if std::env::var("UPDATE_ENUM_COLLISIONS").is_ok() {
let body: String = found
.iter()
.map(|l| format!("{l}\n"))
.collect::<Vec<_>>()
.concat();
std::fs::write(baseline_path(), body).expect("write baseline");
eprintln!("wrote {} known collisions", found.len());
return;
}
let baseline: BTreeSet<String> = std::fs::read_to_string(baseline_path())
.unwrap_or_default()
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.map(str::to_owned)
.collect();
assert!(
!baseline.is_empty() || found.is_empty(),
"baseline is missing or empty; regenerate with UPDATE_ENUM_COLLISIONS=1"
);
let new: Vec<&String> = found.difference(&baseline).collect();
let fixed: Vec<&String> = baseline.difference(&found).collect();
assert!(
new.is_empty(),
"{} enum_map(s) lost invertibility — several EDIFACT codes collapse to one \
BO4E value with no `also_target` to tell them apart, so the reverse will \
substitute the first code for all of them:\n{}",
new.len(),
new.iter()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
);
assert!(
fixed.is_empty(),
"{} listed collision(s) are fixed — drop them from the baseline with \
UPDATE_ENUM_COLLISIONS=1 so the list cannot rot:\n{}",
fixed.len(),
fixed
.iter()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
);
eprintln!("known enum_map collisions: {}", baseline.len());
}