use edifact_mapper::{DataDir, Mapper, ValidationLevel};
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("the crate sits two levels below the workspace root")
.to_path_buf()
}
fn baseline_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/known_sample_violations.txt")
}
fn is_the_known_date_defect(path: Option<&str>) -> bool {
path.map(|p| p.ends_with("C507/2380")).unwrap_or(false)
}
fn violations() -> BTreeSet<String> {
let root = repo_root();
let mapper = match Mapper::from_data_dir(DataDir::path(root.join("dist"))) {
Ok(m) => m,
Err(e) => panic!("the data bundles are a build artefact — run `bundle-data` first: {e}"),
};
let mut out = BTreeSet::new();
let Ok(fvs) = std::fs::read_dir(root.join("fixtures/generated")) else {
return out;
};
for fv_dir in fvs.flatten() {
if !fv_dir.path().is_dir() {
continue;
}
let fv = fv_dir.file_name().to_string_lossy().to_uppercase();
let Ok(msgs) = std::fs::read_dir(fv_dir.path()) else {
continue;
};
for msg_dir in msgs.flatten() {
if !msg_dir.path().is_dir() {
continue;
}
let msg = msg_dir.file_name().to_string_lossy().to_uppercase();
let Ok(files) = std::fs::read_dir(msg_dir.path()) else {
continue;
};
for f in files.flatten() {
let p = f.path();
if p.extension().and_then(|e| e.to_str()) != Some("edi") {
continue;
}
let pid = p
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let Ok(edi) = std::fs::read_to_string(&p) else {
continue;
};
let variants: Vec<String> = if msg == "UTILMD" {
vec![format!("{msg}_Strom"), format!("{msg}_Gas")]
} else {
vec![msg.clone()]
};
let mut report = None;
for v in &variants {
if let Ok(r) =
mapper.validate_edifact_for_pid(&edi, &fv, v, &pid, ValidationLevel::Full)
{
report = Some(r);
break;
}
}
let Some(report) = report else { continue };
for issue in &report.issues {
if format!("{:?}", issue.severity) != "Error" {
continue;
}
if is_the_known_date_defect(issue.field_path.as_deref()) {
continue;
}
let kind = format!("{:?}", issue.kind);
let kind = kind.split(['{', ' ']).next().unwrap_or("?");
let path = issue.field_path.as_deref().unwrap_or("-");
out.insert(format!("{fv} {msg} {pid} {kind} {path}"));
}
}
}
}
out
}
#[test]
fn every_generated_sample_satisfies_its_rulebook() {
let found = violations();
if std::env::var("UPDATE_SAMPLE_CONFORMANCE").is_ok() {
let body: String = found.iter().map(|l| format!("{l}\n")).collect();
std::fs::write(baseline_path(), body).expect("write baseline");
eprintln!("wrote {} sample violation(s)", 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();
let new: Vec<&String> = found.difference(&baseline).collect();
let fixed: Vec<&String> = baseline.difference(&found).collect();
assert!(
new.is_empty(),
"{} generated sample message(s) gained a violation of their own rulebook. \
A sample is produced through the mapping, so this usually means a rule \
started writing something the rulebook does not allow there:\n{}",
new.len(),
new.iter()
.take(30)
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
);
assert!(
fixed.is_empty(),
"{} listed violation(s) are gone — drop them with \
UPDATE_SAMPLE_CONFORMANCE=1 so the list cannot rot:\n{}",
fixed.len(),
fixed
.iter()
.take(30)
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
);
}