automapper-validation 0.14.1

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
// <auto-generated>
// Generated by automapper-generator generate-conditions
// AHB: xml-migs-and-ahbs/FV2504/APERAK_AHB_2_4a_Fehlerkorrektur_20250331.xml
// Generated: 2026-03-12T10:19:40Z
// </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};

/// Generated condition evaluator for APERAK FV2504.
pub struct AperakConditionEvaluatorFV2504 {
    // External condition IDs that require runtime context.
    external_conditions: std::collections::HashSet<u32>,
}

impl Default for AperakConditionEvaluatorFV2504 {
    fn default() -> Self {
        let mut external_conditions = std::collections::HashSet::new();
        external_conditions.insert(3);
        external_conditions.insert(6);
        Self {
            external_conditions,
        }
    }
}

impl ConditionEvaluator for AperakConditionEvaluatorFV2504 {
    fn message_type(&self) -> &str {
        "APERAK"
    }

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

    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
        match condition {
            1 => self.evaluate_1(ctx),
            2 => self.evaluate_2(ctx),
            3 => self.evaluate_3(ctx),
            4 => self.evaluate_4(ctx),
            5 => self.evaluate_5(ctx),
            6 => self.evaluate_6(ctx),
            7 => self.evaluate_7(ctx),
            8 => self.evaluate_8(ctx),
            9 => self.evaluate_9(ctx),
            10 => self.evaluate_10(ctx),
            11 => self.evaluate_11(ctx),
            12 => self.evaluate_12(ctx),
            13 => self.evaluate_13(ctx),
            14 => self.evaluate_14(ctx),
            15 => self.evaluate_15(ctx),
            16 => self.evaluate_16(ctx),
            494 => self.evaluate_494(ctx),
            500 => self.evaluate_500(ctx),
            501 => self.evaluate_501(ctx),
            502 => self.evaluate_502(ctx),
            931 => self.evaluate_931(ctx),
            939 => self.evaluate_939(ctx),
            940 => self.evaluate_940(ctx),
            _ => ConditionResult::Unknown,
        }
    }

    fn is_external(&self, condition: u32) -> bool {
        self.external_conditions.contains(&condition)
    }
    fn is_known(&self, condition: u32) -> bool {
        matches!(
            condition,
            1 | 2
                | 3
                | 4
                | 5
                | 6
                | 7
                | 8
                | 9
                | 10
                | 11
                | 12
                | 13
                | 14
                | 15
                | 16
                | 494
                | 500
                | 501
                | 502
                | 931
                | 939
                | 940
        )
    }
}

impl AperakConditionEvaluatorFV2504 {
    /// [3] Wenn für weitere Fehlerangabe benötigt.
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_3(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external.evaluate("additional_error_detail_needed")
    }

    /// [6] Wenn Fehler innerhalb der Vorgangsebene von IFTSTA, INSRPT, UTILMD oder UTILTS vorhanden.
    /// EXTERNAL: Requires context from outside the message.
    fn evaluate_6(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.external
            .evaluate("transaction_level_error_in_referenced_message")
    }

    /// [1] Wenn SG3 CTA+IC vorhanden.
    fn evaluate_1(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("CTA", 0, "IC")
    }

    /// [2] Wenn fehlerhafter Inhalt vorhanden.
    // REVIEW: In APERAK messages, the ERC (Error Reason Code) segment is present when erroneous content is being reported. Checking for ERC existence is the best proxy for 'fehlerhafter Inhalt vorhanden'. An APERAK with no ERC segments is a pure positive acknowledgment with no errors. (medium confidence)
    fn evaluate_2(&self, ctx: &EvaluationContext) -> ConditionResult {
        // Wenn fehlerhafter Inhalt vorhanden — ERC segment indicates presence of erroneous content
        ConditionResult::from(ctx.has_segment("ERC"))
    }

    /// [4] Wenn in dieser SG4, RFF+TN nicht vorhanden.
    // REVIEW: Checks whether RFF+TN is absent within the current SG4 group's child SG5 groups. Uses parent-child navigation: for each SG4 instance, inspects all SG5 children for RFF with qualifier TN. Falls back to message-wide lacks_qualifier when no navigator is available. Medium confidence because the SG4/SG5 nesting in APERAK may differ from other message types. (medium confidence)
    fn evaluate_4(&self, ctx: &EvaluationContext) -> ConditionResult {
        let nav = match ctx.navigator() {
            Some(n) => n,
            None => return ctx.lacks_qualifier("RFF", 0, "TN"),
        };
        let sg4_count = nav.group_instance_count(&["SG4"]);
        for i in 0..sg4_count {
            let sg5_count = nav.child_group_instance_count(&["SG4"], i, "SG5");
            let has_tn = (0..sg5_count).any(|j| {
                nav.find_segments_in_child_group("RFF", &["SG4"], i, "SG5", j)
                    .iter()
                    .any(|s| {
                        s.elements
                            .first()
                            .and_then(|e| e.first())
                            .is_some_and(|v| v == "TN")
                    })
            });
            if !has_tn {
                return ConditionResult::True;
            }
        }
        ConditionResult::False
    }

    /// [5] Wenn SG4 ERC+Z29 vorhanden.
    fn evaluate_5(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z29")
    }

    /// [7] Wenn SG4 ERC+Z21 vorhanden.
    fn evaluate_7(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z21")
    }

    /// [8] Wenn SG4 ERC+Z16 vorhanden.
    fn evaluate_8(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z16")
    }

    /// [9] Wenn SG4 ERC+Z35 vorhanden.
    fn evaluate_9(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z35")
    }

    /// [10] Wenn SG4 ERC+Z38 vorhanden.
    fn evaluate_10(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z38")
    }

    /// [11] Wenn SG4 ERC+Z39 vorhanden.
    fn evaluate_11(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z39")
    }

    /// [12] Wenn SG4 ERC+Z41 vorhanden.
    fn evaluate_12(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z41")
    }

    /// [13] Wenn SG4 ERC+Z40 vorhanden.
    fn evaluate_13(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.has_qualifier("ERC", 0, "Z40")
    }

    /// [14] Wenn im DE3155 in demselben COM der Code EM vorhanden ist
    fn evaluate_14(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        ConditionResult::from(coms.iter().any(|com| {
            com.elements
                .first()
                .and_then(|e| e.get(1))
                .is_some_and(|v| v == "EM")
        }))
    }

    /// [15] Wenn im DE3155 in demselben COM der Code TE / FX / AJ / AL vorhanden ist
    fn evaluate_15(&self, ctx: &EvaluationContext) -> ConditionResult {
        let coms = ctx.find_segments("COM");
        ConditionResult::from(coms.iter().any(|com| {
            com.elements
                .first()
                .and_then(|e| e.get(1))
                .map(|v| matches!(v.as_str(), "TE" | "FX" | "AJ" | "AL"))
                .unwrap_or(false)
        }))
    }

    /// [16] Wenn der referenzierte Nachrichtentyp IFTSTA, INSRPT, UTILMD oder UTILTS ist.
    // REVIEW: APERAK references another message type. BGM and RFF segments in APERAK carry the referenced message type identifier. Checking all elements of BGM and RFF for the four message type names (IFTSTA, INSRPT, UTILMD, UTILTS) covers the likely encodings. (medium confidence)
    fn evaluate_16(&self, ctx: &EvaluationContext) -> ConditionResult {
        let bgm_segs = ctx.find_segments("BGM");
        let in_bgm = bgm_segs.iter().any(|s| {
            s.elements
                .iter()
                .flatten()
                .any(|v| matches!(v.as_str(), "IFTSTA" | "INSRPT" | "UTILMD" | "UTILTS"))
        });
        if in_bgm {
            return ConditionResult::True;
        }
        let rff_segs = ctx.find_segments("RFF");
        ConditionResult::from(rff_segs.iter().any(|s| {
            s.elements
                .iter()
                .flatten()
                .any(|v| matches!(v.as_str(), "IFTSTA" | "INSRPT" | "UTILMD" | "UTILTS"))
        }))
    }

    /// [494] Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde, oder ein Zeitpunkt, der davor liegt
    // REVIEW: This condition is a declarative constraint: the date in this field must be the document creation time or earlier. It is not a conditional predicate that selects when a field is used, but rather a validation rule that always applies. Without knowing the specific DTM qualifier pair to compare (which field this annotates vs. DTM+137), a full comparison is not implementable. Returning True reflects that the constraint is always in force. (medium confidence)
    fn evaluate_494(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Das hier genannte Datum muss der Zeitpunkt sein, zu dem das Dokument erstellt wurde,
        // oder ein Zeitpunkt, der davor liegt.
        // This is a declarative validation constraint on the field value itself — it always
        // applies when the field is present. The AHB uses this condition to annotate the
        // allowed date range; the constraint is unconditionally in effect.
        ConditionResult::True
    }

    /// [500] Hinweis: Für Folgeprozesse.
    fn evaluate_500(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Für Folgeprozesse — informational note, always applies
        ConditionResult::True
    }

    /// [501] Hinweis: Für Initialprozesse.
    fn evaluate_501(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Für Initialprozesse — informational note, always applies
        ConditionResult::True
    }

    /// [502] Hinweis: Es darf nur eine Information im DE3148 übermittelt werden
    fn evaluate_502(&self, _ctx: &EvaluationContext) -> ConditionResult {
        // Hinweis: Es darf nur eine Information im DE3148 übermittelt werden — informational note, always applies
        ConditionResult::True
    }

    /// [931] Format: ZZZ = +00
    fn evaluate_931(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check("DTM", 0, 1, validate_timezone_utc)
    }

    /// [939] Format: Die Zeichenkette muss die Zeichen @ und . enthalten
    fn evaluate_939(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check("COM", 0, 0, validate_email)
    }

    /// [940] Format: Die Zeichenkette muss mit dem Zeichen + beginnen und danach dürfen nur noch Ziffern folgen
    fn evaluate_940(&self, ctx: &EvaluationContext) -> ConditionResult {
        ctx.format_check("COM", 0, 0, validate_phone)
    }
}