1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//! A distributed bundle must carry the tables its rules name.
//!
//! `VariantCache` is written beside a copy of `code_lists.toml` and repairs
//! itself from it on load. A bundle has no such neighbour: `edifact-data`
//! fetches a bare `edifact-data-<FV>.bin` into `~/.edifact/data`. A bundle that
//! does not carry its tables resolves no name at all — forward, the EDIFACT
//! code reaches the output untranslated; reverse, the BO4E name is written into
//! the EDIFACT slot verbatim.
//!
//! This went unnoticed because `DataBundle::load` calls the *correct*
//! constructor, just with an empty table set, so the debug assertion that
//! guards the wrong constructor could not see it. Every `edifact-mapper`
//! library consumer was affected; the API and the Docker image were not,
//! because they read `mappings/` and `cache/mappings/`, which do have the file.
use std::path::PathBuf;
use mig_bo4e::definition::FieldMapping;
use mig_bo4e::engine::DataBundle;
fn bundle_path(fv: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../dist")
.join(format!("edifact-data-{fv}.bin"))
}
#[test]
fn a_bundle_whose_rules_name_a_code_list_carries_that_list() {
for fv in ["FV2504", "FV2510", "FV2604", "FV2610"] {
let path = bundle_path(fv);
if !path.exists() {
eprintln!("skipping {fv}: no bundle (build artifact)");
continue;
}
let bundle = DataBundle::load(&path).unwrap_or_else(|e| panic!("{fv}: {e}"));
let mut named: Vec<String> = Vec::new();
for variant in bundle.variants.values() {
for defs in std::iter::once(&variant.message_defs)
.chain(variant.transaction_defs.values())
.chain(variant.combined_defs.values())
{
for def in defs {
for mapping in def.fields.values() {
if let FieldMapping::Structured(s) = mapping {
named.extend(s.code_list.clone());
named.extend(s.also_code_list.clone());
}
}
}
}
}
if named.is_empty() {
continue; // this format version names none yet
}
assert!(
!bundle.code_lists.is_empty(),
"{fv}: {} rule position(s) name a shared code list, but the bundle \
carries no tables — every one of them reaches the output raw",
named.len()
);
// and the names have to resolve, not merely be accompanied by *some* tables
let missing: Vec<&String> = named
.iter()
.filter(|n| bundle.code_lists.get(n).is_none())
.collect();
assert!(
missing.is_empty(),
"{fv}: {} named table(s) are absent from the bundle: {:?}",
missing.len(),
missing.iter().take(5).collect::<Vec<_>>()
);
// and every variant's engines must be handed them
for (name, variant) in &bundle.variants {
assert!(
!variant.code_lists.is_empty(),
"{fv}/{name}: the bundle carries tables but this variant was \
not given them, so its engines resolve nothing"
);
}
}
}