automapper-validation 0.9.0

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
// <auto-generated>
// Delegating condition evaluator for MSCONS FV2610.
// Source AHB: MSCONS_AHB_3_2_20260401.xml
//
// Strategy: FV2610's AHB (3.2) preserves 153 of 157 conditions from FV2604
// (AHB 3.1g) byte-for-byte in their description text. This evaluator holds a
// `MsconsConditionEvaluatorFV2604` and delegates unchanged conditions to it,
// overriding only those that are new or semantically different in 3.2.
//
// Rationale: sharing FV2604's bodies — rather than re-stubbing 157 methods —
// means bug fixes applied to FV2604 propagate automatically, and the override
// list is the canonical diff between the two AHB versions.
// </auto-generated>

// LLM-produced bodies aren't idiomatic Rust — silence stylistic lints at
// file scope so `cargo clippy -D warnings` stays green. Correctness,
// suspicious, and perf categories stay on so real bugs still surface.
#![allow(clippy::style, clippy::complexity)]

#[allow(unused_imports)]
use crate::eval::format_validators::*;
use crate::eval::{ConditionEvaluator, ConditionResult, EvaluationContext};
use crate::generated::fv2604::MsconsConditionEvaluatorFV2604;

/// Condition evaluator for MSCONS FV2610.
///
/// Delegates to [`MsconsConditionEvaluatorFV2604`] for conditions whose AHB
/// description didn't change between FV2604 and FV2610. Overrides cover:
/// - [38], [119] — corrected format validators (see `mscons_location_id_test`)
/// - [155] — new in AHB 3.2 (UNB test-indicator check)
///
/// Conditions [502] and [2002] have minor cardinality wording deltas in 3.2
/// but FV2604 returns `True` for both (informational "Hinweis" conditions);
/// the wording change doesn't affect the boolean evaluation, so delegation
/// stays correct.
pub struct MsconsConditionEvaluatorFV2610 {
    fallback: MsconsConditionEvaluatorFV2604,
}

impl Default for MsconsConditionEvaluatorFV2610 {
    fn default() -> Self {
        Self {
            fallback: MsconsConditionEvaluatorFV2604::default(),
        }
    }
}

/// Condition IDs whose FV2610 implementation differs from the FV2604 fallback.
/// Keep this list as the single source of truth for overrides — `evaluate` and
/// `is_known` both consult it.
const FV2610_OVERRIDES: &[u32] = &[38, 119, 155];

impl ConditionEvaluator for MsconsConditionEvaluatorFV2610 {
    fn message_type(&self) -> &str {
        "MSCONS"
    }

    fn format_version(&self) -> &str {
        "FV2610"
    }

    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
        match condition {
            38 => self.evaluate_38(ctx),
            119 => self.evaluate_119(ctx),
            155 => self.evaluate_155(ctx),
            other => self.fallback.evaluate(other, ctx),
        }
    }

    fn is_external(&self, condition: u32) -> bool {
        // No FV2610-specific externals yet — the overridden conditions (38,
        // 119, 155) are all evaluated from segment data.
        self.fallback.is_external(condition)
    }

    fn is_known(&self, condition: u32) -> bool {
        FV2610_OVERRIDES.contains(&condition) || self.fallback.is_known(condition)
    }
}

impl MsconsConditionEvaluatorFV2610 {
    /// [38] wenn in SG6 LOC+172 DE3225 die ID der Messlokation angegeben ist
    fn evaluate_38(&self, ctx: &EvaluationContext) -> ConditionResult {
        // Messlokation = 33-char alphanumeric Zählpunktbezeichnung. A non-empty
        // value that doesn't match (e.g. an 11-digit MaLo-ID) must be False so
        // the AHB's XOR branches against condition [119] stay mutually exclusive.
        let loc_segments = ctx.find_segments_with_qualifier("LOC", 0, "172");
        match loc_segments.first() {
            Some(loc) => match loc.elements.get(1).and_then(|e| e.first()) {
                Some(v) if !v.is_empty() => validate_zahlpunkt(v),
                _ => ConditionResult::False,
            },
            None => ConditionResult::False,
        }
    }

    /// [119] wenn in SG6 LOC+172 DE3225 die ID der Marktlokation angegeben ist
    fn evaluate_119(&self, ctx: &EvaluationContext) -> ConditionResult {
        // Marktlokation = 11-digit MaLo-ID with BDEW check digit. A non-empty
        // value that doesn't match (e.g. a 33-char Zählpunkt) must be False so
        // the AHB's XOR branches against condition [38] stay mutually exclusive.
        let locs = ctx.find_segments_with_qualifier("LOC", 0, "172");
        match locs.first() {
            Some(loc) => match loc.elements.get(1).and_then(|e| e.first()) {
                Some(v) if !v.is_empty() => validate_malo_id(v),
                _ => ConditionResult::False,
            },
            None => ConditionResult::False,
        }
    }

    /// [155] Wenn Übertragungsdatei zu Testzwecken ausgetauscht wird.
    fn evaluate_155(&self, ctx: &EvaluationContext) -> ConditionResult {
        // UNB element 11 (0-indexed 10) is D0035 "Test indicator".
        // Value "1" marks the interchange as a test exchange. Absence means
        // production traffic — False, not Unknown (explicit semantics).
        match ctx.find_segment("UNB") {
            Some(unb) => {
                let test_flag = unb.elements.get(10).and_then(|e| e.first());
                ConditionResult::from(test_flag.is_some_and(|v| v == "1"))
            }
            None => ConditionResult::False,
        }
    }
}