Skip to main content

edi_energy/
any_message.rs

1use edifact_rs::OwnedSegment;
2
3use crate::{
4    CustomRulePack, EdiEnergyMessage, EdiEnergyReport, Error, MessageType, Pruefidentifikator,
5    Release,
6};
7
8#[cfg(feature = "aperak")]
9use crate::messages::aperak::AperakMessage;
10#[cfg(feature = "comdis")]
11use crate::messages::comdis::ComdisMessage;
12#[cfg(feature = "contrl")]
13use crate::messages::contrl::ContrlMessage;
14#[cfg(feature = "iftsta")]
15use crate::messages::iftsta::IftstaMessage;
16#[cfg(feature = "insrpt")]
17use crate::messages::insrpt::InsrptMessage;
18#[cfg(feature = "invoic")]
19use crate::messages::invoic::InvoicMessage;
20#[cfg(feature = "mscons")]
21use crate::messages::mscons::MsconsMessage;
22#[cfg(feature = "ordchg")]
23use crate::messages::ordchg::OrdchgMessage;
24#[cfg(feature = "orders")]
25use crate::messages::orders::OrdersMessage;
26#[cfg(feature = "ordrsp")]
27use crate::messages::ordrsp::OrdrespMessage;
28#[cfg(feature = "partin")]
29use crate::messages::partin::PartinMessage;
30#[cfg(feature = "pricat")]
31use crate::messages::pricat::PricatMessage;
32#[cfg(feature = "quotes")]
33use crate::messages::quotes::QuotesMessage;
34#[cfg(feature = "remadv")]
35use crate::messages::remadv::RemadvMessage;
36#[cfg(feature = "reqote")]
37use crate::messages::reqote::ReqoteMessage;
38#[cfg(feature = "utilmd")]
39use crate::messages::utilmd::UtilmdMessage;
40#[cfg(feature = "utilts")]
41use crate::messages::utilts::UtiltsMessage;
42
43/// A parsed EDI@Energy message, dispatched to its concrete type.
44///
45/// Match on the variants to access type-specific functionality, or use the
46/// [`EdiEnergyMessage`] trait methods (via `msg.validate()`, `msg.serialize()`, etc.)
47/// for common operations across all message types.
48///
49/// Each variant is only present when the corresponding Cargo feature is enabled.
50/// Compile with `--all-features` to enable exhaustive matching — this is the
51/// recommended pattern for dispatch tables and process-layer routers.
52///
53/// The [`Unknown`][AnyMessage::Unknown] variant captures messages whose type is
54/// not compiled in or not recognised, preserving the raw segments for routing
55/// and forwarding use-cases (e.g. BDEW AS4 adapters).
56#[derive(Debug)]
57pub enum AnyMessage {
58    /// UTILMD — Utilities Master Data message.
59    #[cfg(feature = "utilmd")]
60    Utilmd(UtilmdMessage),
61
62    /// MSCONS — Metered Services Consumption Report.
63    #[cfg(feature = "mscons")]
64    Mscons(MsconsMessage),
65
66    /// APERAK — Application Error and Acknowledgement.
67    #[cfg(feature = "aperak")]
68    Aperak(AperakMessage),
69
70    /// CONTRL — Interchange Control Structure (syntax acknowledgement).
71    #[cfg(feature = "contrl")]
72    Contrl(ContrlMessage),
73
74    /// INVOIC — Invoice.
75    #[cfg(feature = "invoic")]
76    Invoic(InvoicMessage),
77
78    /// REMADV — Remittance Advice.
79    #[cfg(feature = "remadv")]
80    Remadv(RemadvMessage),
81
82    /// ORDERS — Purchase Order.
83    #[cfg(feature = "orders")]
84    Orders(OrdersMessage),
85
86    /// IFTSTA — International Multimodal Status Report.
87    #[cfg(feature = "iftsta")]
88    Iftsta(IftstaMessage),
89
90    /// INSRPT — Inspection Report.
91    #[cfg(feature = "insrpt")]
92    Insrpt(InsrptMessage),
93
94    /// REQOTE — Request for Quotation.
95    #[cfg(feature = "reqote")]
96    Reqote(ReqoteMessage),
97
98    /// PARTIN — Party Information.
99    #[cfg(feature = "partin")]
100    Partin(PartinMessage),
101
102    /// ORDCHG — Purchase Order Change.
103    #[cfg(feature = "ordchg")]
104    Ordchg(OrdchgMessage),
105
106    /// ORDRSP — Purchase Order Response.
107    #[cfg(feature = "ordrsp")]
108    Ordrsp(OrdrespMessage),
109
110    /// QUOTES — Quotation.
111    #[cfg(feature = "quotes")]
112    Quotes(QuotesMessage),
113
114    /// COMDIS — Commercial Dispute (Handelsunstimmigkeit).
115    #[cfg(feature = "comdis")]
116    Comdis(ComdisMessage),
117
118    /// PRICAT — Price/Sales Catalogue (Preisliste).
119    #[cfg(feature = "pricat")]
120    Pricat(PricatMessage),
121
122    /// UTILTS — Übertragung technischer Stammdaten (Technical Master Data).
123    #[cfg(feature = "utilts")]
124    Utilts(UtiltsMessage),
125
126    /// A message whose type is not recognised or not compiled into this build.
127    ///
128    /// Raw segments are preserved so callers can inspect, log, or forward the
129    /// message without loss of data. This variant is returned instead of
130    /// `Err(Error::UnknownMessageType)` or `Err(Error::FeatureNotEnabled)` so
131    /// that gateway / routing applications can handle interchanges with mixed
132    /// message types without failing the entire parse.
133    ///
134    /// # Example
135    ///
136    /// ```no_run
137    /// use edi_energy::{Platform, AnyMessage};
138    ///
139    /// let input: &[u8] = &[];
140    /// let msg = Platform::with_all_profiles().parse(input)?;
141    /// if let AnyMessage::Unknown { message_type_code, release, .. } = &msg {
142    ///     eprintln!("Unhandled message type: {} (release {})", message_type_code, release);
143    /// }
144    /// # Ok::<(), edi_energy::Error>(())
145    /// ```
146    Unknown {
147        /// The EDIFACT type code from UNH DE 0065, e.g. `"TRADEX"`.
148        message_type_code: Box<str>,
149        /// The association-assigned release code from UNH DE 0057.
150        release: Release,
151        /// The raw message reference from UNH DE 0062.
152        message_ref: Box<str>,
153        /// All parsed segments (UNH … UNT inclusive).
154        segments: Vec<OwnedSegment>,
155    },
156}
157
158impl AnyMessage {
159    /// Returns `true` if this message has a recognised, compiled-in type.
160    ///
161    /// Returns `false` for the [`Unknown`][AnyMessage::Unknown] variant.
162    #[must_use]
163    pub fn is_known(&self) -> bool {
164        !matches!(self, AnyMessage::Unknown { .. })
165    }
166
167    /// Returns the message type discriminant, or `None` for the
168    /// [`Unknown`][AnyMessage::Unknown] variant.
169    ///
170    /// Prefer this over the [`EdiEnergyMessage::try_message_type`] trait method when
171    /// working with an `AnyMessage` value that may be `Unknown`.
172    #[must_use]
173    pub fn try_message_type(&self) -> Option<MessageType> {
174        // Delegates to the EdiEnergyMessage trait impl, which handles all
175        // feature-gated variants via the delegate_any! macro.
176        <Self as EdiEnergyMessage>::try_message_type(self)
177    }
178
179    // ── Type-specific downcast helpers ────────────────────────────────────────
180
181    /// Returns a reference to the inner [`UtilmdMessage`], or `None`.
182    #[cfg(feature = "utilmd")]
183    #[must_use]
184    pub fn as_utilmd(&self) -> Option<&crate::messages::utilmd::UtilmdMessage> {
185        if let AnyMessage::Utilmd(m) = self {
186            Some(m)
187        } else {
188            None
189        }
190    }
191
192    /// Returns a reference to the inner [`MsconsMessage`], or `None`.
193    #[cfg(feature = "mscons")]
194    #[must_use]
195    pub fn as_mscons(&self) -> Option<&crate::messages::mscons::MsconsMessage> {
196        if let AnyMessage::Mscons(m) = self {
197            Some(m)
198        } else {
199            None
200        }
201    }
202
203    /// Returns a reference to the inner [`AperakMessage`], or `None`.
204    #[cfg(feature = "aperak")]
205    #[must_use]
206    pub fn as_aperak(&self) -> Option<&crate::messages::aperak::AperakMessage> {
207        if let AnyMessage::Aperak(m) = self {
208            Some(m)
209        } else {
210            None
211        }
212    }
213
214    /// Returns a reference to the inner [`ContrlMessage`], or `None`.
215    #[cfg(feature = "contrl")]
216    #[must_use]
217    pub fn as_contrl(&self) -> Option<&crate::messages::contrl::ContrlMessage> {
218        if let AnyMessage::Contrl(m) = self {
219            Some(m)
220        } else {
221            None
222        }
223    }
224
225    /// Returns a reference to the inner [`InvoicMessage`], or `None`.
226    #[cfg(feature = "invoic")]
227    #[must_use]
228    pub fn as_invoic(&self) -> Option<&crate::messages::invoic::InvoicMessage> {
229        if let AnyMessage::Invoic(m) = self {
230            Some(m)
231        } else {
232            None
233        }
234    }
235
236    /// Returns a reference to the inner [`RemadvMessage`], or `None`.
237    #[cfg(feature = "remadv")]
238    #[must_use]
239    pub fn as_remadv(&self) -> Option<&crate::messages::remadv::RemadvMessage> {
240        if let AnyMessage::Remadv(m) = self {
241            Some(m)
242        } else {
243            None
244        }
245    }
246
247    /// Returns a reference to the inner [`OrdersMessage`], or `None`.
248    #[cfg(feature = "orders")]
249    #[must_use]
250    pub fn as_orders(&self) -> Option<&crate::messages::orders::OrdersMessage> {
251        if let AnyMessage::Orders(m) = self {
252            Some(m)
253        } else {
254            None
255        }
256    }
257
258    /// Returns a reference to the inner [`IftstaMessage`], or `None`.
259    #[cfg(feature = "iftsta")]
260    #[must_use]
261    pub fn as_iftsta(&self) -> Option<&crate::messages::iftsta::IftstaMessage> {
262        if let AnyMessage::Iftsta(m) = self {
263            Some(m)
264        } else {
265            None
266        }
267    }
268
269    /// Returns a reference to the inner [`InsrptMessage`], or `None`.
270    #[cfg(feature = "insrpt")]
271    #[must_use]
272    pub fn as_insrpt(&self) -> Option<&crate::messages::insrpt::InsrptMessage> {
273        if let AnyMessage::Insrpt(m) = self {
274            Some(m)
275        } else {
276            None
277        }
278    }
279
280    /// Returns a reference to the inner [`ReqoteMessage`], or `None`.
281    #[cfg(feature = "reqote")]
282    #[must_use]
283    pub fn as_reqote(&self) -> Option<&crate::messages::reqote::ReqoteMessage> {
284        if let AnyMessage::Reqote(m) = self {
285            Some(m)
286        } else {
287            None
288        }
289    }
290
291    /// Returns a reference to the inner [`PartinMessage`], or `None`.
292    #[cfg(feature = "partin")]
293    #[must_use]
294    pub fn as_partin(&self) -> Option<&crate::messages::partin::PartinMessage> {
295        if let AnyMessage::Partin(m) = self {
296            Some(m)
297        } else {
298            None
299        }
300    }
301
302    /// Returns a reference to the inner [`OrdchgMessage`], or `None`.
303    #[cfg(feature = "ordchg")]
304    #[must_use]
305    pub fn as_ordchg(&self) -> Option<&crate::messages::ordchg::OrdchgMessage> {
306        if let AnyMessage::Ordchg(m) = self {
307            Some(m)
308        } else {
309            None
310        }
311    }
312
313    /// Returns a reference to the inner [`OrdrespMessage`], or `None`.
314    #[cfg(feature = "ordrsp")]
315    #[must_use]
316    pub fn as_ordrsp(&self) -> Option<&crate::messages::ordrsp::OrdrespMessage> {
317        if let AnyMessage::Ordrsp(m) = self {
318            Some(m)
319        } else {
320            None
321        }
322    }
323
324    /// Returns a reference to the inner [`QuotesMessage`], or `None`.
325    #[cfg(feature = "quotes")]
326    #[must_use]
327    pub fn as_quotes(&self) -> Option<&crate::messages::quotes::QuotesMessage> {
328        if let AnyMessage::Quotes(m) = self {
329            Some(m)
330        } else {
331            None
332        }
333    }
334
335    /// Returns a reference to the inner [`ComdisMessage`], or `None`.
336    #[cfg(feature = "comdis")]
337    #[must_use]
338    pub fn as_comdis(&self) -> Option<&crate::messages::comdis::ComdisMessage> {
339        if let AnyMessage::Comdis(m) = self {
340            Some(m)
341        } else {
342            None
343        }
344    }
345
346    /// Returns a reference to the inner [`PricatMessage`], or `None`.
347    #[cfg(feature = "pricat")]
348    #[must_use]
349    pub fn as_pricat(&self) -> Option<&crate::messages::pricat::PricatMessage> {
350        if let AnyMessage::Pricat(m) = self {
351            Some(m)
352        } else {
353            None
354        }
355    }
356
357    /// Returns a reference to the inner [`UtiltsMessage`], or `None`.
358    #[cfg(feature = "utilts")]
359    #[must_use]
360    pub fn as_utilts(&self) -> Option<&crate::messages::utilts::UtiltsMessage> {
361        if let AnyMessage::Utilts(m) = self {
362            Some(m)
363        } else {
364            None
365        }
366    }
367
368    /// Returns the inner [`MessageCore`][crate::messages::core::MessageCore], or `None`
369    /// for the [`Unknown`][AnyMessage::Unknown] variant.
370    ///
371    /// Used internally by [`Platform`][crate::Platform] for registry-aware validation.
372    #[cfg(any(
373        feature = "utilmd",
374        feature = "mscons",
375        feature = "aperak",
376        feature = "contrl",
377        feature = "invoic",
378        feature = "remadv",
379        feature = "orders",
380        feature = "iftsta",
381        feature = "insrpt",
382        feature = "reqote",
383        feature = "partin",
384        feature = "ordchg",
385        feature = "ordrsp",
386        feature = "quotes",
387        feature = "comdis",
388        feature = "pricat",
389        feature = "utilts",
390    ))]
391    #[must_use]
392    pub(crate) fn message_core(&self) -> Option<&crate::messages::core::MessageCore> {
393        match self {
394            #[cfg(feature = "utilmd")]
395            AnyMessage::Utilmd(m) => Some(&m.core),
396            #[cfg(feature = "mscons")]
397            AnyMessage::Mscons(m) => Some(&m.core),
398            #[cfg(feature = "aperak")]
399            AnyMessage::Aperak(m) => Some(&m.core),
400            #[cfg(feature = "contrl")]
401            AnyMessage::Contrl(m) => Some(&m.core),
402            #[cfg(feature = "invoic")]
403            AnyMessage::Invoic(m) => Some(&m.core),
404            #[cfg(feature = "remadv")]
405            AnyMessage::Remadv(m) => Some(&m.core),
406            #[cfg(feature = "orders")]
407            AnyMessage::Orders(m) => Some(&m.core),
408            #[cfg(feature = "iftsta")]
409            AnyMessage::Iftsta(m) => Some(&m.core),
410            #[cfg(feature = "insrpt")]
411            AnyMessage::Insrpt(m) => Some(&m.core),
412            #[cfg(feature = "reqote")]
413            AnyMessage::Reqote(m) => Some(&m.core),
414            #[cfg(feature = "partin")]
415            AnyMessage::Partin(m) => Some(&m.core),
416            #[cfg(feature = "ordchg")]
417            AnyMessage::Ordchg(m) => Some(&m.core),
418            #[cfg(feature = "ordrsp")]
419            AnyMessage::Ordrsp(m) => Some(&m.core),
420            #[cfg(feature = "quotes")]
421            AnyMessage::Quotes(m) => Some(&m.core),
422            #[cfg(feature = "comdis")]
423            AnyMessage::Comdis(m) => Some(&m.core),
424            #[cfg(feature = "pricat")]
425            AnyMessage::Pricat(m) => Some(&m.core),
426            #[cfg(feature = "utilts")]
427            AnyMessage::Utilts(m) => Some(&m.core),
428            AnyMessage::Unknown { .. } => None,
429        }
430    }
431}
432
433// ── EdiEnergyMessage delegation ──────────────────────────────────────────────
434
435macro_rules! delegate_any {
436    ($self:expr, $inner:ident => $body:expr) => {
437        match $self {
438            #[cfg(feature = "utilmd")]
439            AnyMessage::Utilmd($inner) => $body,
440            #[cfg(feature = "mscons")]
441            AnyMessage::Mscons($inner) => $body,
442            #[cfg(feature = "aperak")]
443            AnyMessage::Aperak($inner) => $body,
444            #[cfg(feature = "contrl")]
445            AnyMessage::Contrl($inner) => $body,
446            #[cfg(feature = "invoic")]
447            AnyMessage::Invoic($inner) => $body,
448            #[cfg(feature = "remadv")]
449            AnyMessage::Remadv($inner) => $body,
450            #[cfg(feature = "orders")]
451            AnyMessage::Orders($inner) => $body,
452            #[cfg(feature = "iftsta")]
453            AnyMessage::Iftsta($inner) => $body,
454            #[cfg(feature = "insrpt")]
455            AnyMessage::Insrpt($inner) => $body,
456            #[cfg(feature = "reqote")]
457            AnyMessage::Reqote($inner) => $body,
458            #[cfg(feature = "partin")]
459            AnyMessage::Partin($inner) => $body,
460            #[cfg(feature = "ordchg")]
461            AnyMessage::Ordchg($inner) => $body,
462            #[cfg(feature = "ordrsp")]
463            AnyMessage::Ordrsp($inner) => $body,
464            #[cfg(feature = "quotes")]
465            AnyMessage::Quotes($inner) => $body,
466            #[cfg(feature = "comdis")]
467            AnyMessage::Comdis($inner) => $body,
468            #[cfg(feature = "pricat")]
469            AnyMessage::Pricat($inner) => $body,
470            #[cfg(feature = "utilts")]
471            AnyMessage::Utilts($inner) => $body,
472            AnyMessage::Unknown {
473                message_type_code,
474                release,
475                message_ref: _,
476                segments,
477            } => {
478                let _ = (message_type_code, release, segments);
479                unreachable!("Unknown variant must be handled before delegation")
480            }
481        }
482    };
483}
484
485// With `--no-default-features` all typed variants are removed and only
486// `Unknown` remains, making the `_ =>` delegation arms in each match
487// structurally unreachable.  The logic is correct; suppress the lint.
488#[allow(unreachable_patterns)]
489impl EdiEnergyMessage for AnyMessage {
490    fn try_message_type(&self) -> Option<MessageType> {
491        match self {
492            AnyMessage::Unknown { .. } => None,
493            _ => delegate_any!(self, m => m.try_message_type()),
494        }
495    }
496
497    fn detect_release(&self) -> Result<&Release, Error> {
498        match self {
499            AnyMessage::Unknown { release, .. } => Ok(release),
500            _ => delegate_any!(self, m => m.detect_release()),
501        }
502    }
503
504    fn message_ref(&self) -> &str {
505        match self {
506            AnyMessage::Unknown { message_ref, .. } => message_ref,
507            _ => delegate_any!(self, m => m.message_ref()),
508        }
509    }
510
511    fn detect_pruefidentifikator(&self) -> Result<Pruefidentifikator, Error> {
512        match self {
513            AnyMessage::Unknown { .. } => Err(Error::MissingPruefidentifikator),
514            _ => delegate_any!(self, m => m.detect_pruefidentifikator()),
515        }
516    }
517
518    fn validate(&self) -> Result<EdiEnergyReport, Error> {
519        match self {
520            AnyMessage::Unknown {
521                message_type_code, ..
522            } => {
523                use edifact_rs::{ValidationIssue, ValidationReport, ValidationSeverity};
524                let mut inner = ValidationReport::default();
525                inner.add_error(
526                    ValidationIssue::new(
527                        ValidationSeverity::Error,
528                        format!(
529                            "message type {message_type_code:?} is not validated in this build"
530                        ),
531                    )
532                    .with_rule_id("UNKNOWN-MSG-TYPE"),
533                );
534                Ok(EdiEnergyReport::new(inner))
535            }
536            _ => delegate_any!(self, m => m.validate()),
537        }
538    }
539
540    #[allow(unused_variables)]
541    fn validate_against(&self, release: &Release) -> Result<EdiEnergyReport, Error> {
542        match self {
543            AnyMessage::Unknown {
544                message_type_code, ..
545            } => {
546                use edifact_rs::{ValidationIssue, ValidationReport, ValidationSeverity};
547                let mut inner = ValidationReport::default();
548                inner.add_error(
549                    ValidationIssue::new(
550                        ValidationSeverity::Error,
551                        format!(
552                            "message type {message_type_code:?} is not validated in this build"
553                        ),
554                    )
555                    .with_rule_id("UNKNOWN-MSG-TYPE"),
556                );
557                Ok(EdiEnergyReport::new(inner))
558            }
559            _ => delegate_any!(self, m => m.validate_against(release)),
560        }
561    }
562
563    fn serialize(&self) -> Result<Vec<u8>, Error> {
564        match self {
565            AnyMessage::Unknown { segments, .. } => {
566                edifact_rs::segments_to_bytes_owned(segments).map_err(Error::Parse)
567            }
568            _ => delegate_any!(self, m => m.serialize()),
569        }
570    }
571
572    fn segments(&self) -> &[OwnedSegment] {
573        match self {
574            AnyMessage::Unknown { segments, .. } => segments,
575            _ => delegate_any!(self, m => m.segments()),
576        }
577    }
578
579    fn validate_with_pack(&self, extra: CustomRulePack) -> Result<EdiEnergyReport, Error> {
580        match self {
581            AnyMessage::Unknown { .. } => {
582                let _ = extra;
583                Err(Error::MissingRelease)
584            }
585            _ => delegate_any!(self, m => m.validate_with_pack(extra)),
586        }
587    }
588
589    #[allow(unused_variables)]
590    fn validate_on_date(&self, reference_date: time::Date) -> Result<EdiEnergyReport, Error> {
591        match self {
592            AnyMessage::Unknown {
593                message_type_code, ..
594            } => {
595                use edifact_rs::{ValidationIssue, ValidationReport, ValidationSeverity};
596                let mut inner = ValidationReport::default();
597                inner.add_error(
598                    ValidationIssue::new(
599                        ValidationSeverity::Error,
600                        format!(
601                            "message type {message_type_code:?} is not validated in this build"
602                        ),
603                    )
604                    .with_rule_id("UNKNOWN-MSG-TYPE"),
605                );
606                Ok(EdiEnergyReport::new(inner))
607            }
608            _ => delegate_any!(self, m => m.validate_on_date(reference_date)),
609        }
610    }
611}