automapper-validation 0.7.0

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
//! BDEW Artikel-ID / Artikelnummer codelist provider.
//!
//! Resolves UTILMD/PRICAT/INVOIC conditions that reference the
//! "Codeliste der Artikelnummern und Artikel-ID" — conditions like
//! [73] ("Wenn … ein Code aus der Codeliste … in der Spalte
//! UTILMD/Preisangabe mit X gekennzeichnet ist").

use serde::Deserialize;
use std::collections::HashMap;
use std::sync::OnceLock;

const EMBEDDED_JSON: &str = include_str!("../../data/artikel_id_codelist.json");

#[derive(Debug, Deserialize)]
struct RawFile {
    version: String,
    codes: HashMap<String, RawEntry>,
}

#[derive(Debug, Deserialize)]
struct RawEntry {
    description: String,
    einheit: String,
    utilmd: MessageFlags,
    pricat: MessageFlags,
    invoic: MessageFlags,
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub struct MessageFlags {
    pub codeverwendung: bool,
    pub preisangabe: bool,
}

#[derive(Debug, Clone)]
pub struct ArtikelIdEntry {
    pub description: String,
    pub einheit: String,
    pub utilmd: MessageFlags,
    pub pricat: MessageFlags,
    pub invoic: MessageFlags,
}

#[derive(Debug, Clone, Copy)]
pub enum CodelistColumn {
    UtilmdCodeverwendung,
    UtilmdPreisangabe,
    PricatCodeverwendung,
    PricatPreisangabe,
    InvoicCodeverwendung,
    InvoicPreisangabe,
}

#[derive(Debug)]
pub struct ArtikelIdCodelist {
    version: String,
    codes: HashMap<String, ArtikelIdEntry>,
}

impl ArtikelIdCodelist {
    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
        let raw: RawFile = serde_json::from_str(json)?;
        let codes = raw
            .codes
            .into_iter()
            .map(|(k, v)| {
                (
                    k,
                    ArtikelIdEntry {
                        description: v.description,
                        einheit: v.einheit,
                        utilmd: v.utilmd,
                        pricat: v.pricat,
                        invoic: v.invoic,
                    },
                )
            })
            .collect();
        Ok(Self {
            version: raw.version,
            codes,
        })
    }

    pub fn embedded() -> &'static Self {
        static CELL: OnceLock<ArtikelIdCodelist> = OnceLock::new();
        CELL.get_or_init(|| {
            ArtikelIdCodelist::from_json(EMBEDDED_JSON)
                .expect("embedded artikel_id_codelist.json is malformed")
        })
    }

    pub fn version(&self) -> &str {
        &self.version
    }

    pub fn flag(&self, code: &str, column: CodelistColumn) -> Option<bool> {
        let entry = self.codes.get(code)?;
        use CodelistColumn::*;
        Some(match column {
            UtilmdCodeverwendung => entry.utilmd.codeverwendung,
            UtilmdPreisangabe => entry.utilmd.preisangabe,
            PricatCodeverwendung => entry.pricat.codeverwendung,
            PricatPreisangabe => entry.pricat.preisangabe,
            InvoicCodeverwendung => entry.invoic.codeverwendung,
            InvoicPreisangabe => entry.invoic.preisangabe,
        })
    }

    pub fn contains(&self, code: &str) -> bool {
        self.codes.contains_key(code)
    }
}