use std::collections::BTreeMap;
use std::path::PathBuf;
use mig_assembly::tokenize::parse_to_segments;
const REFERENCES: &[(&str, &[&str])] = &[
("Z18", &["Z16", "Z22"]), ("Z19", &["Z17"]), ("Z32", &["Z18"]), ("Z37", &["Z20"]), ("Z38", &["Z19"]), ];
fn fixtures() -> Vec<PathBuf> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../fixtures/generated");
let mut out = Vec::new();
for fv in std::fs::read_dir(&root).expect("fixtures/generated") {
let dir = fv.unwrap().path().join("utilmd");
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
out.extend(
entries
.map(|e| e.unwrap().path())
.filter(|p| p.extension().is_some_and(|x| x == "edi")),
);
}
out.sort();
out
}
fn violations(edi: &[u8]) -> Vec<String> {
let segments = parse_to_segments(edi).expect("sample tokenizes");
let mut out = Vec::new();
let mut locs: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut refs: Vec<(String, String)> = Vec::new();
let mut in_sg8_or_sg12 = false;
let mut tx = 0;
let check = |tx: usize,
locs: &BTreeMap<String, Vec<String>>,
refs: &[(String, String)],
out: &mut Vec<String>| {
for (qualifier, id) in refs {
let Some((_, kinds)) = REFERENCES.iter().find(|(q, _)| q == qualifier) else {
continue;
};
let Some(ids) = kinds.iter().find_map(|k| locs.get(*k)) else {
continue; };
if !ids.contains(id) {
out.push(format!(
"tx {tx}: RFF+{qualifier}:{id} names none of {ids:?}"
));
}
}
};
for seg in &segments {
match seg.id.as_str() {
"IDE" | "UNT" => {
if tx > 0 {
check(tx, &locs, &refs, &mut out);
}
locs.clear();
refs.clear();
in_sg8_or_sg12 = false;
tx += 1;
}
"LOC" if !in_sg8_or_sg12 => {
let id = seg.get_component(1, 0);
if !id.is_empty() {
locs.entry(seg.get_element(0).to_string())
.or_default()
.push(id.to_string());
}
}
"SEQ" | "NAD" => in_sg8_or_sg12 = true,
"RFF" if in_sg8_or_sg12 => {
let id = seg.get_component(0, 1);
if !id.is_empty() {
refs.push((seg.get_component(0, 0).to_string(), id.to_string()));
}
}
_ => {}
}
}
out
}
#[test]
fn every_location_reference_names_a_loc_of_its_transaction() {
let mut failures = Vec::new();
let samples = fixtures();
assert!(
samples.len() > 100,
"found only {} UTILMD samples",
samples.len()
);
for path in &samples {
let edi = std::fs::read(path).unwrap();
for v in violations(&edi) {
failures.push(format!("{}: {v}", path.display()));
}
}
assert!(
failures.is_empty(),
"{} location references name no LOC of their transaction:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn a_reference_to_another_location_is_caught() {
let edi = b"UNA:+.? 'UNH+1+UTILMD:D:11A:UN:S2.1'IDE+24+X'LOC+Z18+E1814774964'\
SEQ+ZD7'RFF+Z32:E8032120031'UNT+5+1'";
let v = violations(edi);
assert_eq!(v.len(), 1, "{v:?}");
let ok = b"UNA:+.? 'UNH+1+UTILMD:D:11A:UN:S2.1'IDE+24+X'LOC+Z18+E1814774964'\
SEQ+ZD7'RFF+Z32:E1814774964'UNT+5+1'";
assert!(violations(ok).is_empty());
}