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}