use edifact_mapper::{CodeForm, DataDir, FromEdifactOptions, Mapper};
use serde_json::Value;
use std::path::PathBuf;
const FV: &str = "FV2604";
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.expect("crate is two levels under the workspace root")
.to_path_buf()
}
fn setup() -> Option<Mapper> {
let root = repo_root();
if !root.join(format!("dist/edifact-data-{FV}.bin")).exists() {
eprintln!("skipping: build the {FV} bundle first");
return None;
}
Some(Mapper::from_data_dir(DataDir::path(root.join("dist")).eager(&[FV])).expect("load bundle"))
}
fn fixture(msg_dir: &str, pid: &str) -> String {
let path = repo_root().join(format!(
"fixtures/generated/{}/{msg_dir}/{pid}.edi",
FV.to_lowercase()
));
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
}
const RAW: FromEdifactOptions = FromEdifactOptions {
codes: CodeForm::Raw,
};
fn values_of(v: &Value, key: &str, out: &mut Vec<String>) {
match v {
Value::Object(o) => {
for (k, child) in o {
if k == key {
match child {
Value::String(s) => out.push(s.clone()),
Value::Object(e) => {
if let Some(Value::String(s)) = e.get("code") {
out.push(s.clone());
}
}
_ => {}
}
}
values_of(child, key, out);
}
}
Value::Array(a) => a.iter().for_each(|c| values_of(c, key, out)),
_ => {}
}
}
fn wire_qualifiers(edifact: &str, tag: &str) -> Vec<String> {
edifact
.split('\'')
.map(str::trim)
.filter_map(|s| s.strip_prefix(&format!("{tag}+")))
.map(|rest| rest.split(['+', ':']).next().unwrap_or("").to_string())
.collect()
}
#[test]
fn raw_codes_render_the_same_message_as_names() {
let Some(mapper) = setup() else {
return;
};
for (variant, dir, pid) in [
("UTILMD_Strom", "utilmd", "55001"),
("UTILMD_Strom", "utilmd", "55043"),
("UTILMD_Strom", "utilmd", "55168"),
("IFTSTA", "iftsta", "21010"),
("IFTSTA", "iftsta", "21011"),
] {
let edifact = fixture(dir, pid);
let named = mapper
.from_edifact::<Value, Value>(&edifact, FV, variant, pid)
.expect("names");
let raw = mapper
.from_edifact_with::<Value, Value>(&edifact, FV, variant, pid, &RAW)
.expect("raw");
assert_ne!(
serde_json::to_value(&named.nachrichten[0]).unwrap(),
serde_json::to_value(&raw.nachrichten[0]).unwrap(),
"{pid}: raw codes changed nothing — the option does not reach the engine"
);
let from_names = mapper
.to_edifact_nachricht(&named.nachrichten[0], FV, variant, pid)
.expect("render names");
let from_raw = mapper
.to_edifact_nachricht(&raw.nachrichten[0], FV, variant, pid)
.expect("render raw");
assert_eq!(from_raw, from_names, "{pid}: raw codes render differently");
}
}
#[test]
fn code_fields_hold_the_wire_codes() {
let Some(mapper) = setup() else {
return;
};
let edifact = fixture("utilmd", "55043");
let raw = mapper
.from_edifact_with::<Value, Value>(&edifact, FV, "UTILMD_Strom", "55043", &RAW)
.expect("raw");
let json = serde_json::to_value(&raw.nachrichten[0]).unwrap();
let mut roles = Vec::new();
values_of(&json, "partnerrolle", &mut roles);
let mut wire: Vec<String> = wire_qualifiers(&edifact, "NAD")
.into_iter()
.filter(|q| q != "MS" && q != "MR")
.collect();
roles.sort();
wire.sort();
assert_eq!(roles, wire, "partnerrolle should be the NAD qualifiers");
let mut types = Vec::new();
values_of(&json, "nachrichtentyp", &mut types);
assert_eq!(types, wire_qualifiers(&edifact, "BGM"));
}
#[test]
fn enriched_raw_codes_keep_their_meaning() {
let Some(mapper) = setup() else {
return;
};
let edifact = fixture("utilmd", "55043");
let raw = mapper
.from_edifact_with::<Value, Value>(&edifact, FV, "UTILMD_Strom", "55043", &RAW)
.expect("raw");
let json = serde_json::to_value(&raw.nachrichten[0]).unwrap();
let mut enriched = Vec::new();
fn walk(v: &Value, out: &mut Vec<(String, Value)>) {
match v {
Value::Object(o) => {
if let (Some(Value::String(c)), Some(m)) = (o.get("code"), o.get("meaning")) {
out.push((c.clone(), m.clone()));
}
o.values().for_each(|c| walk(c, out));
}
Value::Array(a) => a.iter().for_each(|c| walk(c, out)),
_ => {}
}
}
walk(&json, &mut enriched);
assert!(!enriched.is_empty(), "no enriched code in the raw output");
let explained = enriched.iter().filter(|(_, m)| m.is_string()).count();
assert!(
explained * 2 >= enriched.len(),
"most raw codes should carry their AHB meaning: {explained}/{}",
enriched.len()
);
}