use automapper_validation::{
ConditionEvaluator, ConditionExprEvaluator, ConditionResult, EvaluationContext,
MapExternalProvider,
};
use edifact_mapper::element_scopes::entity_element_scopes;
use edifact_mapper::{
validate_with_conditions, validate_with_conditions_in_scopes, DataDir, Mapper,
};
use mig_assembly::assembler::{AssembledGroup, AssembledTree};
use mig_assembly::navigator::AssembledTreeNavigator;
use mig_types::segment::OwnedSegment;
use serde_json::Value;
use std::path::PathBuf;
fn setup(fv: &str) -> Option<(Mapper, Value)> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
if !root.join(format!("dist/edifact-data-{fv}.bin")).exists() {
eprintln!("skipping: build the {fv} bundle first");
return None;
}
let mapper =
Mapper::from_data_dir(DataDir::path(root.join("dist")).eager(&[fv])).expect("bundle");
let dump: Value = serde_json::from_str(
&std::fs::read_to_string(
root.join("crates/edifact-mapper/tests/data/mako_twin_55043_zaehler_ohne_smgw.json"),
)
.expect("dump"),
)
.expect("json");
Some((mapper, dump[fv].clone()))
}
#[test]
fn every_element_is_scoped_to_its_own_instance() {
for fv in ["FV2604", "FV2610"] {
let Some((mapper, json)) = setup(fv) else {
continue;
};
let engine = mapper.engine(fv, "UTILMD_Strom", "55043").expect("engine");
let tree = engine.map_all_reverse(&json, None);
let scopes = entity_element_scopes(&engine, &json, &tree);
let scope = |key: &str, index: usize| {
scopes
.get(&(key.to_string(), Some(index)))
.cloned()
.unwrap_or_else(|| panic!("{fv}: no scope for {key}[{index}]"))
};
let obis = [
scope("obisZaehleinrichtung", 0),
scope("obisZaehleinrichtung", 1),
];
let meter_with_smgw = scope("zaehlerDaten", 2);
for path in obis.iter().chain([&meter_with_smgw]) {
let groups: Vec<&str> = path.iter().map(|(g, _)| g.as_str()).collect();
assert_eq!(groups, ["SG4", "SG8"], "{fv}: {path:?}");
}
assert_ne!(obis[0], obis[1], "{fv}");
assert!(!obis.contains(&meter_with_smgw), "{fv}");
let sg8 = &tree
.groups
.iter()
.find(|g| g.group_id == "SG4")
.unwrap()
.repetitions[meter_with_smgw[0].1]
.child_groups
.iter()
.find(|g| g.group_id == "SG8")
.unwrap()
.repetitions[meter_with_smgw[1].1];
assert!(
sg8.segments
.iter()
.any(|s| s.tag == "RFF" && s.elements[0][0] == "Z14"),
"{fv}"
);
assert!(
json["prozessdaten"].is_object()
== scopes.contains_key(&("prozessdaten".to_string(), None)),
"{fv}"
);
}
}
struct Only420Scoped;
impl ConditionEvaluator for Only420Scoped {
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
if condition != 420 {
return ConditionResult::True;
}
ConditionResult::from(
ctx.scoped_find_segments_in("SG8", "RFF")
.iter()
.any(|s| s.get_element(0) == "Z14"),
)
}
fn is_external(&self, _: u32) -> bool {
false
}
fn message_type(&self) -> &str {
"UTILMD"
}
fn format_version(&self) -> &str {
"FV2604"
}
}
fn flat(tree: &AssembledTree) -> Vec<OwnedSegment> {
fn walk(groups: &[AssembledGroup], out: &mut Vec<OwnedSegment>) {
for g in groups {
for r in &g.repetitions {
for s in &r.segments {
out.push(OwnedSegment {
id: s.tag.clone(),
elements: s.elements.clone(),
segment_number: out.len() as u32,
});
}
walk(&r.child_groups, out);
}
}
}
let mut out = Vec::new();
walk(&tree.groups, &mut out);
out
}
#[test]
fn an_obis_group_is_judged_by_its_own_sg8() {
for fv in ["FV2604", "FV2610"] {
let Some((mapper, json)) = setup(fv) else {
continue;
};
let engine = mapper.engine(fv, "UTILMD_Strom", "55043").expect("engine");
let requirements = mapper
.pid_requirements(fv, "UTILMD_Strom", "55043")
.expect("requirements");
let tree = engine.map_all_reverse(&json, None);
let segments = flat(&tree);
let navigator = AssembledTreeNavigator::new(&tree);
let external = MapExternalProvider::new(Default::default());
let ctx = EvaluationContext::with_navigator("55043", &external, &segments, &navigator);
let evaluator = Only420Scoped;
let expr_eval = ConditionExprEvaluator::new(&evaluator);
let konfiguration = |errors: Vec<edifact_mapper::PidValidationError>| -> usize {
errors
.iter()
.filter(|e| {
e.to_string()
.contains("ObisZaehleinrichtung.konfigurationsId")
})
.count()
};
assert_eq!(
konfiguration(validate_with_conditions(
&json,
&requirements,
&expr_eval,
&ctx
)),
2,
"{fv}"
);
let scopes = entity_element_scopes(&engine, &json, &tree);
assert_eq!(
konfiguration(validate_with_conditions_in_scopes(
&json,
&requirements,
&expr_eval,
&ctx,
&scopes,
&Default::default()
)),
0,
"{fv}"
);
}
}
#[test]
fn every_nested_list_element_is_scoped_to_its_own_instance() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let fixture = root.join("fixtures/generated/fv2604/utilmd/55043.edi");
let Some((mapper, _)) = setup("FV2604") else {
return;
};
let edifact = std::fs::read_to_string(fixture).expect("fixture");
let ic: mig_bo4e::model::Interchange<Value, Value> = mapper
.from_edifact(&edifact, "FV2604", "UTILMD_Strom", "55043")
.expect("parse");
let tx = &ic.nachrichten[0].transaktionen[0];
let mut json = tx["stammdaten"].clone();
mig_bo4e::model::restore_entity(
&mut json,
mig_bo4e::model::TX_METADATA_ENTITY,
tx.get("transaktionsdaten").unwrap_or(&Value::Null),
);
let engine = mapper
.engine("FV2604", "UTILMD_Strom", "55043")
.expect("engine");
let tree = engine.map_all_reverse(&json, None);
let scopes = edifact_mapper::element_scopes::nested_element_scopes(&engine, &json, &tree);
let element = ("trancheDaten".to_string(), None);
let of = |index: usize| {
scopes
.get(&(element.clone(), vec![("zuordnungen".to_string(), index)]))
.cloned()
.unwrap_or_else(|| panic!("no scope for trancheDaten.zuordnungen[{index}]"))
};
let (first, second) = (of(0), of(1));
for path in [&first, &second] {
let groups: Vec<&str> = path.iter().map(|(g, _)| g.as_str()).collect();
assert_eq!(groups, ["SG4", "SG8", "SG10"], "{path:?}");
}
assert_eq!(first[..2], second[..2], "the same SG8");
assert_ne!(first[2], second[2], "different SG10s");
}