edifact-mapper 0.8.0

EDIFACT to BO4E bidirectional conversion for the German energy market
Documentation
//! `from_edifact_with_diagnostics` must tell the caller when the BO4E result is
//! missing content the EDIFACT carried.
//!
//! The conversion still succeeds — a segment outside the PID's AHB simply has no
//! BO4E representation — but silently dropping it is what made truncated results
//! look like clean ones, so the caller has to be able to see it.

use std::path::PathBuf;

use edifact_mapper::{DataDir, Mapper};
use mig_assembly::StructureDiagnosticKind;

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()
}

type Dynamic = mig_bo4e::model::Interchange<serde_json::Value, serde_json::Value>;

#[test]
fn diagnostics_report_ahb_foreign_segments() {
    let root = repo_root();
    let fixture = root.join("fixtures/generated/fv2504/utilmd/55001.edi");
    if !root.join("dist/edifact-data-FV2504.bin").exists() || !fixture.exists() {
        eprintln!("skipping: regenerate the FV2504 bundle + fixture first");
        return;
    }
    let mapper = Mapper::from_data_dir(DataDir::path(root.join("dist"))).expect("load bundle");
    let clean = std::fs::read_to_string(&fixture).expect("read fixture");

    let (clean_ic, clean_diags): (Dynamic, _) = mapper
        .from_edifact_with_diagnostics(&clean, "FV2504", "UTILMD_Strom", "55001")
        .expect("convert clean fixture");
    assert!(
        clean_diags.is_empty(),
        "a fixture that matches its AHB should convert without diagnostics, got: {clean_diags:?}"
    );

    // FTX is a real UTILMD segment, but PID 55001's AHB does not list it.
    let with_foreign = clean.replace("RFF+Z13:55001'", "RFF+Z13:55001'FTX+ACB+++Hallo'");
    let (foreign_ic, foreign_diags): (Dynamic, _) = mapper
        .from_edifact_with_diagnostics(&with_foreign, "FV2504", "UTILMD_Strom", "55001")
        .expect("an AHB-foreign segment must not fail the conversion");

    let skipped: Vec<_> = foreign_diags
        .iter()
        .filter(|d| d.kind == StructureDiagnosticKind::SkippedUnknownSegment)
        .collect();
    assert_eq!(
        skipped.len(),
        1,
        "expected one skipped-segment diagnostic, got: {foreign_diags:?}"
    );
    assert_eq!(skipped[0].segment_id, "FTX");

    // Everything the AHB does cover still maps: only the foreign segment is lost.
    assert_eq!(
        serde_json::to_value(&foreign_ic.nachrichten[0].transaktionen).unwrap(),
        serde_json::to_value(&clean_ic.nachrichten[0].transaktionen).unwrap(),
        "the AHB-covered content must be unaffected by the foreign segment"
    );
}