edifact-mapper 0.30.5

EDIFACT to BO4E bidirectional conversion for the German energy market
Documentation
//! A Zählzeit code (UTILMD SG10 CCI+Z39/Z38 C240/7037) is `an..3` in the MIG's
//! `Format_Specification` and `an..17` in the EDIFACT standard. The PID
//! schemas used to carry the standard format, so a 12-character code passed
//! both length checks in every format version — while FV2504, whose rule
//! translates the CCI codes of the same list, drew `TooLong` on the
//! translated *names* (`uebertragungsnetzbetreiber`), which are not what is
//! sent.

use edifact_mapper::{DataDir, IssueKind, Mapper, PidValidationError, ValidationLevel};
use serde_json::Value;
use std::path::PathBuf;

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

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
}

/// `validate_pid` findings about length, over every transaction.
fn bo4e_too_long(mapper: &Mapper, edi: &str, fv: &str) -> Vec<String> {
    let ic: Dynamic = mapper
        .from_edifact(edi, fv, "UTILMD_Strom", "55043")
        .unwrap();
    let mut out = Vec::new();
    for tx in &ic.nachrichten[0].transaktionen {
        let mut s = tx["stammdaten"].clone();
        mig_bo4e::model::restore_entity(
            &mut s,
            mig_bo4e::model::TX_METADATA_ENTITY,
            tx.get("transaktionsdaten").unwrap_or(&Value::Null),
        );
        for e in mapper
            .validate_pid(&s, fv, "UTILMD_Strom", "55043")
            .unwrap()
        {
            if let PidValidationError::TooLong { field, length, .. } = e {
                out.push(format!("{field} {length}"));
            }
        }
    }
    out
}

fn edifact_too_long(mapper: &Mapper, edi: &str, fv: &str) -> Vec<String> {
    mapper
        .validate_edifact_for_pid(edi, fv, "UTILMD_Strom", "55043", ValidationLevel::Full)
        .unwrap()
        .issues
        .iter()
        .filter_map(|i| match &i.kind {
            IssueKind::ValueTooLong { length, max, .. } => Some(format!(
                "{} {length}>{max}",
                i.actual_value.clone().unwrap_or_default()
            )),
            _ => None,
        })
        .collect()
}

#[test]
fn a_zaehlzeit_code_longer_than_three_characters_is_too_long_in_every_fv() {
    let root = repo_root();
    let Ok(mapper) = Mapper::from_data_dir(DataDir::path(root.join("dist"))) else {
        eprintln!("skipping: no bundles");
        return;
    };
    for fv in ["FV2504", "FV2510", "FV2604", "FV2610"] {
        let path = root.join(format!(
            "fixtures/generated/{}/utilmd/55043.edi",
            fv.to_lowercase()
        ));
        let edi = std::fs::read_to_string(&path).unwrap();

        // The sample as generated: no length finding, translated names included.
        assert_eq!(
            edifact_too_long(&mapper, &edi, fv),
            Vec::<String>::new(),
            "{fv}"
        );
        assert_eq!(
            bo4e_too_long(&mapper, &edi, fv),
            Vec::<String>::new(),
            "{fv}"
        );

        // One Zählzeit code of 12 characters.
        let start = edi.find("CCI+Z39++").expect("a Zählzeit CCI") + "CCI+Z39++".len();
        let end = start + edi[start..].find('\'').unwrap();
        let long = format!("{}ZDNBNAPG56ZM{}", &edi[..start], &edi[end..]);

        assert_eq!(
            edifact_too_long(&mapper, &long, fv),
            vec!["ZDNBNAPG56ZM 12>3".to_string()],
            "{fv}"
        );
        assert_eq!(
            bo4e_too_long(&mapper, &long, fv).len(),
            1,
            "{fv}: validate_pid must report the 12-character code"
        );
    }
}