bo4e-edifact-types 0.1.26

Generic interchange model types for BO4E/EDIFACT conversion
Documentation
use serde::{Deserialize, Serialize};

/// EDIFACT interchange envelope metadata (UNB/UNZ).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Interchangedaten {
    pub syntax_kennung: Option<String>,
    pub absender_code: Option<String>,
    pub empfaenger_code: Option<String>,
    pub datum: Option<String>,
    pub zeit: Option<String>,
    pub interchange_ref: Option<String>,
}

/// EDIFACT message metadata (UNH/UNT).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Nachrichtendaten {
    pub unh_referenz: String,
    pub nachrichten_typ: String,
}

/// A complete EDIFACT interchange. M = message stammdaten, T = transaction stammdaten.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Interchange<M, T> {
    pub interchangedaten: Interchangedaten,
    pub nachrichten: Vec<Nachricht<M, T>>,
}

/// A single EDIFACT message within an interchange.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Nachricht<M, T> {
    pub nachrichtendaten: Nachrichtendaten,
    pub stammdaten: M,
    pub transaktionen: Vec<T>,
}

/// Dynamic variant for untyped usage.
pub type DynamicInterchange = Interchange<serde_json::Value, serde_json::Value>;
/// Dynamic variant for untyped message usage.
pub type DynamicNachricht = Nachricht<serde_json::Value, serde_json::Value>;

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_dynamic_interchange_serde_roundtrip() {
        let interchange: DynamicInterchange = Interchange {
            interchangedaten: Interchangedaten {
                syntax_kennung: Some("UNOC".to_string()),
                absender_code: Some("1234567890123".to_string()),
                empfaenger_code: Some("9876543210987".to_string()),
                datum: Some("20260326".to_string()),
                zeit: Some("1430".to_string()),
                interchange_ref: Some("00001".to_string()),
            },
            nachrichten: vec![Nachricht {
                nachrichtendaten: Nachrichtendaten {
                    unh_referenz: "00001".to_string(),
                    nachrichten_typ: "UTILMD".to_string(),
                },
                stammdaten: json!({"key": "value"}),
                transaktionen: vec![json!({"tx": 1}), json!({"tx": 2})],
            }],
        };

        let json_str = serde_json::to_string(&interchange).unwrap();
        let deserialized: DynamicInterchange = serde_json::from_str(&json_str).unwrap();

        assert_eq!(
            deserialized.interchangedaten.syntax_kennung,
            Some("UNOC".to_string())
        );
        assert_eq!(
            deserialized.interchangedaten.absender_code,
            Some("1234567890123".to_string())
        );
        assert_eq!(
            deserialized.interchangedaten.empfaenger_code,
            Some("9876543210987".to_string())
        );
        assert_eq!(deserialized.nachrichten.len(), 1);
        assert_eq!(
            deserialized.nachrichten[0].nachrichtendaten.unh_referenz,
            "00001"
        );
        assert_eq!(
            deserialized.nachrichten[0].nachrichtendaten.nachrichten_typ,
            "UTILMD"
        );
        assert_eq!(
            deserialized.nachrichten[0].stammdaten,
            json!({"key": "value"})
        );
        assert_eq!(deserialized.nachrichten[0].transaktionen.len(), 2);
    }

    #[test]
    fn test_typed_interchange_serde_roundtrip() {
        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
        struct TestStamm {
            name: String,
        }

        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
        struct TestTx {
            id: u32,
        }

        let interchange: Interchange<TestStamm, TestTx> = Interchange {
            interchangedaten: Interchangedaten::default(),
            nachrichten: vec![Nachricht {
                nachrichtendaten: Nachrichtendaten {
                    unh_referenz: "REF1".to_string(),
                    nachrichten_typ: "ORDERS".to_string(),
                },
                stammdaten: TestStamm {
                    name: "test".to_string(),
                },
                transaktionen: vec![TestTx { id: 1 }, TestTx { id: 2 }],
            }],
        };

        let json_str = serde_json::to_string(&interchange).unwrap();
        let deserialized: Interchange<TestStamm, TestTx> = serde_json::from_str(&json_str).unwrap();

        assert_eq!(deserialized.nachrichten.len(), 1);
        assert_eq!(
            deserialized.nachrichten[0].stammdaten,
            TestStamm {
                name: "test".to_string()
            }
        );
        assert_eq!(deserialized.nachrichten[0].transaktionen.len(), 2);
        assert_eq!(
            deserialized.nachrichten[0].transaktionen[0],
            TestTx { id: 1 }
        );
        assert_eq!(
            deserialized.nachrichten[0].transaktionen[1],
            TestTx { id: 2 }
        );
    }

    #[test]
    fn test_interchangedaten_camel_case_serialization() {
        let data = Interchangedaten {
            syntax_kennung: Some("UNOC".to_string()),
            absender_code: Some("SENDER".to_string()),
            empfaenger_code: None,
            datum: None,
            zeit: None,
            interchange_ref: Some("REF".to_string()),
        };

        let json_value: serde_json::Value = serde_json::to_value(&data).unwrap();
        let obj = json_value.as_object().unwrap();

        // Verify camelCase keys
        assert!(obj.contains_key("syntaxKennung"));
        assert!(obj.contains_key("absenderCode"));
        assert!(obj.contains_key("empfaengerCode"));
        assert!(obj.contains_key("datum"));
        assert!(obj.contains_key("zeit"));
        assert!(obj.contains_key("interchangeRef"));

        // Verify no snake_case keys
        assert!(!obj.contains_key("syntax_kennung"));
        assert!(!obj.contains_key("absender_code"));
        assert!(!obj.contains_key("empfaenger_code"));
        assert!(!obj.contains_key("interchange_ref"));
    }
}