Skip to main content

edifact_rs/
event.rs

1//! Event model for EDIFACT (de)serialization.
2//!
3//! [`EdifactEvent`] is the borrowed, zero-allocation form used during real-time
4//! emission.  [`OwnedEdifactEvent`] is the owned form collected by [`VecEmitter`]
5//! for testing and introspection — no `Box::leak` anywhere.
6
7use crate::EdifactError;
8use std::io::Write;
9
10// ── event types ───────────────────────────────────────────────────────────────
11
12/// A borrowed EDIFACT event emitted during serialization.
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum EdifactEvent<'a> {
16    /// Beginning of a new segment (e.g. `"BGM"`, `"NAD"`).
17    StartSegment {
18        /// Segment tag.
19        tag: &'a str,
20    },
21    /// A data element value — first (or only) component of a new element.
22    Element {
23        /// Element text value.
24        value: &'a str,
25    },
26    /// An additional component within the current element.
27    ComponentElement {
28        /// Component text value.
29        value: &'a str,
30    },
31    /// End of the current segment.
32    EndSegment,
33}
34
35/// An owned EDIFACT event — for collection and testing (no borrowed lifetimes).
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum OwnedEdifactEvent {
39    /// Owned segment-start event.
40    StartSegment {
41        /// Segment tag.
42        tag: String,
43    },
44    /// Owned element event.
45    Element {
46        /// Element text value.
47        value: String,
48    },
49    /// Owned component event.
50    ComponentElement {
51        /// Component text value.
52        value: String,
53    },
54    /// Owned segment-end event.
55    EndSegment,
56}
57
58impl<'a> EdifactEvent<'a> {
59    /// Convert to an owned event, cloning string data.
60    pub fn into_owned(self) -> OwnedEdifactEvent {
61        match self {
62            Self::StartSegment { tag } => OwnedEdifactEvent::StartSegment {
63                tag: tag.to_owned(),
64            },
65            Self::Element { value } => OwnedEdifactEvent::Element {
66                value: value.to_owned(),
67            },
68            Self::ComponentElement { value } => OwnedEdifactEvent::ComponentElement {
69                value: value.to_owned(),
70            },
71            Self::EndSegment => OwnedEdifactEvent::EndSegment,
72        }
73    }
74}
75
76// ── emitter trait ─────────────────────────────────────────────────────────────
77
78/// Trait for any sink that can consume [`EdifactEvent`]s.
79pub trait EventEmitter {
80    /// Consume one event.
81    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError>;
82
83    /// Return the decimal-mark byte used by the interchange (`b'.'` by default).
84    ///
85    /// Serializers that format numeric values (e.g. [`crate::ser::DecimalFloat`])
86    /// call this to discover whether to emit `12.5` or `12,5`.
87    ///
88    /// The default implementation returns `b'.'`, which is correct for standard
89    /// EDIFACT interchanges that do not declare a UNA service string or that use
90    /// the ISO 9735 default.  Override this in emitters backed by a
91    /// [`crate::Writer`] with a custom [`crate::tokenizer::ServiceStringAdvice`].
92    #[inline]
93    fn decimal_mark(&self) -> u8 {
94        b'.'
95    }
96}
97
98// ── VecEmitter ────────────────────────────────────────────────────────────────
99
100/// Collects events into a [`Vec<OwnedEdifactEvent>`].
101///
102/// Useful for testing and introspection.  Does not leak memory.
103#[derive(Debug, Default)]
104pub struct VecEmitter {
105    /// Collected owned events.
106    pub events: Vec<OwnedEdifactEvent>,
107}
108
109impl EventEmitter for VecEmitter {
110    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
111        self.events.push(event.into_owned());
112        Ok(())
113    }
114}
115
116// ── WriterEmitter ─────────────────────────────────────────────────────────────
117
118/// Internal protocol-state machine for [`WriterEmitter`].
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120enum EmitterState {
121    /// Between segments: no open segment.
122    Idle,
123    /// A [`EdifactEvent::StartSegment`] has been emitted; no element written yet.
124    InSegment,
125    /// An [`EdifactEvent::Element`] has been emitted; `ComponentElement` is valid.
126    InElement,
127}
128
129/// Writes EDIFACT events directly to any [`Write`] implementation.
130///
131/// Each event is written to the underlying writer immediately — no intermediate
132/// buffering of element strings occurs, so no heap allocation is required per
133/// event.  This makes `WriterEmitter` suitable for high-throughput serialization
134/// of large EDIFACT messages.
135///
136/// # Protocol
137///
138/// Events must arrive in the order produced by [`crate::EdifactSerialize`]:
139/// `StartSegment` → zero or more (`Element` → zero or more `ComponentElement`) → `EndSegment`.
140///
141/// Any violation of this protocol returns
142/// [`EdifactError::InvalidEventSequence`] immediately.  Violations are
143/// detected in both debug and release builds.
144pub struct WriterEmitter<W: Write> {
145    writer: crate::Writer<W>,
146    state: EmitterState,
147}
148
149impl<W: Write> WriterEmitter<W> {
150    /// Create a new `WriterEmitter` with default EDIFACT delimiters.
151    pub fn new(inner: W) -> Self {
152        Self {
153            writer: crate::Writer::new(inner),
154            state: EmitterState::Idle,
155        }
156    }
157
158    /// Create a new `WriterEmitter` with custom delimiters, writing a UNA header first.
159    ///
160    /// # Errors
161    ///
162    /// Returns [`EdifactError::InvalidUna`] when `ssa.is_valid()` is false.
163    pub fn with_una(
164        inner: W,
165        ssa: crate::tokenizer::ServiceStringAdvice,
166    ) -> Result<Self, crate::EdifactError> {
167        Ok(Self {
168            writer: crate::Writer::with_una(inner, ssa)?,
169            state: EmitterState::Idle,
170        })
171    }
172
173    /// Flush and consume the emitter, returning the underlying writer.
174    pub fn finish(self) -> Result<W, EdifactError> {
175        self.writer.finish()
176    }
177
178    /// Number of complete segments written so far.
179    pub fn segment_count(&self) -> u64 {
180        self.writer.segment_count()
181    }
182
183    /// Return the active [`ServiceStringAdvice`][crate::tokenizer::ServiceStringAdvice].
184    ///
185    /// Callers can use this to format values (e.g., floats) using the correct
186    /// decimal-mark character configured in the UNA header.
187    pub fn service_string_advice(&self) -> crate::tokenizer::ServiceStringAdvice {
188        self.writer.service_string_advice()
189    }
190}
191
192impl<W: Write> EventEmitter for WriterEmitter<W> {
193    #[inline]
194    fn decimal_mark(&self) -> u8 {
195        self.writer.service_string_advice().decimal_mark
196    }
197
198    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
199        match event {
200            EdifactEvent::StartSegment { tag } => {
201                if self.state != EmitterState::Idle {
202                    return Err(EdifactError::InvalidEventSequence {
203                        message: "StartSegment emitted while a segment is already open; emit EndSegment first",
204                    });
205                }
206                self.state = EmitterState::InSegment;
207                self.writer.write_tag_only(tag)?;
208            }
209            EdifactEvent::Element { value } => {
210                if self.state == EmitterState::Idle {
211                    return Err(EdifactError::InvalidEventSequence {
212                        message: "Element emitted outside of a segment; emit StartSegment first",
213                    });
214                }
215                self.state = EmitterState::InElement;
216                self.writer.write_element_sep()?;
217                self.writer.write_escaped(value)?;
218            }
219            EdifactEvent::ComponentElement { value } => {
220                if self.state != EmitterState::InElement {
221                    return Err(EdifactError::InvalidEventSequence {
222                        message: "ComponentElement emitted without a preceding Element in the same segment",
223                    });
224                }
225                self.writer.write_component_sep()?;
226                self.writer.write_escaped(value)?;
227            }
228            EdifactEvent::EndSegment => {
229                if self.state == EmitterState::Idle {
230                    return Err(EdifactError::InvalidEventSequence {
231                        message: "EndSegment emitted while no segment is open; emit StartSegment first",
232                    });
233                }
234                self.state = EmitterState::Idle;
235                self.writer.write_segment_term_and_count()?;
236            }
237        }
238        Ok(())
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn vec_emitter_no_memory_leak() {
248        let mut e = VecEmitter::default();
249        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
250        e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
251        e.emit(EdifactEvent::EndSegment).unwrap();
252        assert_eq!(
253            e.events[0],
254            OwnedEdifactEvent::StartSegment {
255                tag: "BGM".to_owned()
256            }
257        );
258        assert_eq!(
259            e.events[1],
260            OwnedEdifactEvent::Element {
261                value: "E03".to_owned()
262            }
263        );
264    }
265
266    #[test]
267    fn writer_emitter_produces_valid_edifact() {
268        let mut buf = Vec::new();
269        {
270            let mut e = WriterEmitter::new(&mut buf);
271            e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
272            e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
273            e.emit(EdifactEvent::Element { value: "11042" }).unwrap();
274            e.emit(EdifactEvent::EndSegment).unwrap();
275            e.finish().unwrap();
276        }
277        assert_eq!(buf, b"BGM+E03+11042'");
278    }
279
280    #[test]
281    fn writer_emitter_handles_components() {
282        let mut buf = Vec::new();
283        {
284            let mut e = WriterEmitter::new(&mut buf);
285            e.emit(EdifactEvent::StartSegment { tag: "NAD" }).unwrap();
286            e.emit(EdifactEvent::Element { value: "MS" }).unwrap();
287            e.emit(EdifactEvent::Element {
288                value: "9900112233445",
289            })
290            .unwrap();
291            e.emit(EdifactEvent::ComponentElement { value: "" })
292                .unwrap();
293            e.emit(EdifactEvent::ComponentElement { value: "293" })
294                .unwrap();
295            e.emit(EdifactEvent::EndSegment).unwrap();
296            e.finish().unwrap();
297        }
298        let s = std::str::from_utf8(&buf).unwrap();
299        assert_eq!(s, "NAD+MS+9900112233445::293'");
300    }
301
302    // ── protocol-violation tests (BUG 2.1) ───────────────────────────────────
303
304    #[test]
305    fn writer_emitter_element_before_start_segment_is_err() {
306        let mut e = WriterEmitter::new(Vec::<u8>::new());
307        let err = e.emit(EdifactEvent::Element { value: "X" }).unwrap_err();
308        assert!(
309            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
310            "expected InvalidEventSequence, got {err:?}"
311        );
312    }
313
314    #[test]
315    fn writer_emitter_component_before_element_is_err() {
316        let mut e = WriterEmitter::new(Vec::<u8>::new());
317        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
318        let err = e
319            .emit(EdifactEvent::ComponentElement { value: "X" })
320            .unwrap_err();
321        assert!(
322            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
323            "expected InvalidEventSequence, got {err:?}"
324        );
325    }
326
327    #[test]
328    fn writer_emitter_double_start_segment_is_err() {
329        let mut e = WriterEmitter::new(Vec::<u8>::new());
330        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
331        let err = e
332            .emit(EdifactEvent::StartSegment { tag: "DTM" })
333            .unwrap_err();
334        assert!(
335            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
336            "expected InvalidEventSequence, got {err:?}"
337        );
338    }
339
340    #[test]
341    fn writer_emitter_end_segment_without_start_is_err() {
342        let mut e = WriterEmitter::new(Vec::<u8>::new());
343        let err = e.emit(EdifactEvent::EndSegment).unwrap_err();
344        assert!(
345            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
346            "expected InvalidEventSequence, got {err:?}"
347        );
348    }
349}