Skip to main content

edi_energy/
message_type.rs

1use std::fmt;
2
3/// EDIFACT message type codes used in the German energy market (EDI@Energy).
4///
5/// All variants are always present regardless of enabled features; feature gates
6/// control which concrete message structs and profile data are compiled in.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum MessageType {
10    /// UTILMD — Utilities Master Data.\
11    /// BDEW message for grid-connection processes (switchover, registration, etc.).
12    Utilmd,
13    /// MSCONS — Metered Services Consumption Report.\
14    /// Meter value transmission between grid operator and balance-group manager.
15    Mscons,
16    /// APERAK — Application Error and Acknowledgement.\
17    /// Technical rejection or acknowledgement of a previously received message.
18    Aperak,
19    /// CONTRL — Interchange Control Structure.\
20    /// Syntax acknowledgement at interchange level.
21    Contrl,
22    /// INVOIC — Invoice.
23    Invoic,
24    /// REMADV — Remittance Advice.
25    Remadv,
26    /// ORDERS — Purchase Order.
27    Orders,
28    /// IFTSTA — International Multimodal Status Report Message.
29    Iftsta,
30    /// INSRPT — Inspection Report.
31    Insrpt,
32    /// REQOTE — Request for Quotation.
33    Reqote,
34    /// PARTIN — Party Information.
35    Partin,
36    /// ORDCHG — Purchase Order Change.
37    Ordchg,
38    /// ORDRSP — Purchase Order Response.
39    Ordrsp,
40    /// QUOTES — Quotation.
41    Quotes,
42    /// COMDIS — Commercial Dispute (Handelsunstimmigkeit).
43    Comdis,
44    /// PRICAT — Price/Sales Catalogue (Preisliste).
45    Pricat,
46    /// UTILTS — Übertragung technischer Stammdaten (Technical Master Data).
47    Utilts,
48}
49
50/// The one table: variant, wire code, and the Cargo feature that compiles it in.
51///
52/// Everything else is derived from it, so adding a message type is one row. A
53/// missing `from_unh_code` arm would be silent — it turns a supported message
54/// into `AnyMessage::Unknown`.
55macro_rules! message_types {
56    ($(($variant:ident, $code:literal, $feature:literal)),* $(,)?) => {
57        impl MessageType {
58            /// Returns the EDIFACT type code as it appears in the UNH segment
59            /// (e.g. `"UTILMD"`).
60            #[must_use]
61            pub fn as_str(self) -> &'static str {
62                match self {
63                    $(Self::$variant => $code,)*
64                }
65            }
66
67            /// Parses the type code from a UNH segment string slice.
68            ///
69            /// Returns `None` for unrecognised codes.
70            #[must_use]
71            pub fn from_unh_code(code: &str) -> Option<Self> {
72                match code {
73                    $($code => Some(Self::$variant),)*
74                    _ => None,
75                }
76            }
77
78            /// The Cargo feature that must be enabled to parse this type.
79            #[must_use]
80            pub fn feature_name(self) -> &'static str {
81                match self {
82                    $(Self::$variant => $feature,)*
83                }
84            }
85
86            /// Returns `true` when this type's Cargo feature is compiled in.
87            #[must_use]
88            pub fn is_feature_enabled(self) -> bool {
89                match self {
90                    $(Self::$variant => cfg!(feature = $feature),)*
91                }
92            }
93
94            /// Every message type, in declaration order.
95            pub const ALL: &'static [Self] = &[$(Self::$variant),*];
96        }
97    };
98}
99
100message_types![
101    (Utilmd, "UTILMD", "utilmd"),
102    (Mscons, "MSCONS", "mscons"),
103    (Aperak, "APERAK", "aperak"),
104    (Contrl, "CONTRL", "contrl"),
105    (Invoic, "INVOIC", "invoic"),
106    (Remadv, "REMADV", "remadv"),
107    (Orders, "ORDERS", "orders"),
108    (Iftsta, "IFTSTA", "iftsta"),
109    (Insrpt, "INSRPT", "insrpt"),
110    (Reqote, "REQOTE", "reqote"),
111    (Partin, "PARTIN", "partin"),
112    (Ordchg, "ORDCHG", "ordchg"),
113    (Ordrsp, "ORDRSP", "ordrsp"),
114    (Quotes, "QUOTES", "quotes"),
115    (Comdis, "COMDIS", "comdis"),
116    (Pricat, "PRICAT", "pricat"),
117    (Utilts, "UTILTS", "utilts"),
118];
119
120impl fmt::Display for MessageType {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.write_str(self.as_str())
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::MessageType;
129
130    /// Every variant must round-trip through its wire code, and no two may share
131    /// one.
132    #[test]
133    fn the_table_is_a_bijection() {
134        let mut codes: Vec<&str> = Vec::new();
135        for &mt in MessageType::ALL {
136            assert_eq!(MessageType::from_unh_code(mt.as_str()), Some(mt));
137            assert_eq!(mt.feature_name(), mt.as_str().to_lowercase());
138            codes.push(mt.as_str());
139        }
140        let total = codes.len();
141        codes.sort_unstable();
142        codes.dedup();
143        assert_eq!(codes.len(), total, "two message types share a wire code");
144        assert_eq!(MessageType::from_unh_code("NOMINT"), None);
145    }
146}