Skip to main content

edifact_rs/
event.rs

1//! Event model for EDIFACT serialization.
2//!
3//! [`EdifactEvent`] carries its text as a [`Cow`], so one type serves both the
4//! zero-allocation emission path (where every value borrows from the value being
5//! serialized) and [`VecEmitter`], which has to keep events past the borrow that
6//! produced them.
7
8use crate::EdifactError;
9use std::borrow::Cow;
10use std::io::Write;
11
12// ── event types ───────────────────────────────────────────────────────────────
13
14/// A borrowed EDIFACT event emitted during serialization.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum EdifactEvent<'a> {
18    /// Beginning of a new segment (e.g. `"BGM"`, `"NAD"`).
19    StartSegment {
20        /// Segment tag.
21        tag: Cow<'a, str>,
22    },
23    /// A data element value — first (or only) component of a new element.
24    Element {
25        /// Element text value.
26        value: Cow<'a, str>,
27    },
28    /// An additional component within the current element.
29    ComponentElement {
30        /// Component text value.
31        value: Cow<'a, str>,
32    },
33    /// The first component of a further occurrence of the current data element.
34    ///
35    /// The write-side mirror of [`Token::RepeatElement`][crate::Token::RepeatElement]:
36    /// the parser splits repeating data elements, so the serializer has to be
37    /// able to produce them, or a value that round-trips through a typed struct
38    /// comes back collapsed into one occurrence.
39    ///
40    /// Requires an active repetition separator — see
41    /// [`ServiceStringAdvice::is_repetition_active`][crate::ServiceStringAdvice::is_repetition_active].
42    /// Without one there is no byte to separate occurrences with, so
43    /// [`WriterEmitter`] returns
44    /// [`EdifactError::RepetitionSeparatorNotDeclared`] rather than emitting
45    /// output that reads back as a single occurrence.
46    RepeatElement {
47        /// First component of the new occurrence.
48        value: Cow<'a, str>,
49    },
50    /// End of the current segment.
51    EndSegment,
52}
53
54impl<'a> EdifactEvent<'a> {
55    /// A [`StartSegment`][Self::StartSegment] event for `tag`.
56    #[inline]
57    #[must_use]
58    pub fn start(tag: impl Into<Cow<'a, str>>) -> Self {
59        Self::StartSegment { tag: tag.into() }
60    }
61
62    /// An [`Element`][Self::Element] event carrying `value`.
63    #[inline]
64    #[must_use]
65    pub fn element(value: impl Into<Cow<'a, str>>) -> Self {
66        Self::Element {
67            value: value.into(),
68        }
69    }
70
71    /// A [`ComponentElement`][Self::ComponentElement] event carrying `value`.
72    #[inline]
73    #[must_use]
74    pub fn component(value: impl Into<Cow<'a, str>>) -> Self {
75        Self::ComponentElement {
76            value: value.into(),
77        }
78    }
79
80    /// A [`RepeatElement`][Self::RepeatElement] event carrying `value`.
81    #[inline]
82    #[must_use]
83    pub fn repeat(value: impl Into<Cow<'a, str>>) -> Self {
84        Self::RepeatElement {
85            value: value.into(),
86        }
87    }
88}
89
90impl EdifactEvent<'_> {
91    /// Detach this event from the value it borrows from, cloning its text.
92    #[must_use]
93    pub fn into_owned(self) -> EdifactEvent<'static> {
94        match self {
95            Self::StartSegment { tag } => EdifactEvent::start(Cow::Owned(tag.into_owned())),
96            Self::Element { value } => EdifactEvent::element(Cow::Owned(value.into_owned())),
97            Self::ComponentElement { value } => {
98                EdifactEvent::component(Cow::Owned(value.into_owned()))
99            }
100            Self::RepeatElement { value } => EdifactEvent::repeat(Cow::Owned(value.into_owned())),
101            Self::EndSegment => EdifactEvent::EndSegment,
102        }
103    }
104}
105
106// ── emitter trait ─────────────────────────────────────────────────────────────
107
108/// Trait for any sink that can consume [`EdifactEvent`]s.
109pub trait EventEmitter {
110    /// Consume one event.
111    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError>;
112
113    /// Return the decimal-mark byte used by the interchange (`b'.'` by default).
114    ///
115    /// Serializers that format numeric values (e.g. [`crate::ser::DecimalFloat`])
116    /// call this to discover whether to emit `12.5` or `12,5`.
117    ///
118    /// The default implementation returns `b'.'`, which is correct for standard
119    /// EDIFACT interchanges that do not declare a UNA service string or that use
120    /// the ISO 9735 default.  Override this in emitters backed by a
121    /// [`crate::Writer`] with a custom [`crate::ServiceStringAdvice`].
122    #[inline]
123    fn decimal_mark(&self) -> u8 {
124        b'.'
125    }
126}
127
128// ── VecEmitter ────────────────────────────────────────────────────────────────
129
130/// Collects events into a `Vec<EdifactEvent<'static>>`.
131///
132/// Useful for testing and introspection.  Does not leak memory.
133#[derive(Debug, Default)]
134pub struct VecEmitter {
135    /// Collected owned events.
136    pub events: Vec<EdifactEvent<'static>>,
137}
138
139impl EventEmitter for VecEmitter {
140    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
141        self.events.push(event.into_owned());
142        Ok(())
143    }
144}
145
146// ── WriterEmitter ─────────────────────────────────────────────────────────────
147
148/// Internal protocol-state machine for [`WriterEmitter`].
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150enum EmitterState {
151    /// Between segments: no open segment.
152    Idle,
153    /// A [`EdifactEvent::StartSegment`] has been emitted; no element written yet.
154    InSegment,
155    /// An [`EdifactEvent::Element`] has been emitted; `ComponentElement` is valid.
156    InElement,
157}
158
159/// Writes EDIFACT events directly to any [`Write`] implementation.
160///
161/// Each event is written to the underlying writer immediately — no intermediate
162/// buffering of element strings occurs, so no heap allocation is required per
163/// event.  This makes `WriterEmitter` suitable for high-throughput serialization
164/// of large EDIFACT messages.
165///
166/// # Protocol
167///
168/// Events must arrive in the order produced by [`crate::EdifactSerialize`]:
169/// `StartSegment` → zero or more (`Element` → zero or more `ComponentElement`) → `EndSegment`.
170///
171/// Any violation of this protocol returns
172/// [`EdifactError::InvalidEventSequence`] immediately.  Violations are
173/// detected in both debug and release builds.
174pub struct WriterEmitter<W: Write> {
175    writer: crate::Writer<W>,
176    state: EmitterState,
177}
178
179impl<W: Write> WriterEmitter<W> {
180    /// Create a new `WriterEmitter` with default EDIFACT delimiters.
181    pub fn new(inner: W) -> Self {
182        Self {
183            writer: crate::Writer::new(inner),
184            state: EmitterState::Idle,
185        }
186    }
187
188    /// Create a new `WriterEmitter` with custom delimiters, writing a UNA header first.
189    ///
190    /// # Errors
191    ///
192    /// Returns [`EdifactError::InvalidUna`] when `ssa.is_valid()` is false.
193    pub fn with_una(
194        inner: W,
195        ssa: crate::tokenizer::ServiceStringAdvice,
196    ) -> Result<Self, crate::EdifactError> {
197        Ok(Self {
198            writer: crate::Writer::with_una(inner, ssa)?,
199            state: EmitterState::Idle,
200        })
201    }
202
203    /// Bind this emitter's writer to a character repertoire.
204    ///
205    /// The typed serialization path had no way to reach
206    /// [`Writer::with_charset`][crate::Writer::with_charset], so a `#[derive(EdifactSerialize)]`
207    /// struct could only ever go out as UTF-8 — which is wrong for every
208    /// `UNOC`…`UNOK` partner, and wrong in the silent way: `ü` arrives as two
209    /// mojibake characters rather than as an error.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// use edifact_rs::{Charset, EdifactEvent, EventEmitter, WriterEmitter};
215    ///
216    /// let mut emitter = WriterEmitter::new(Vec::new()).with_charset(Charset::UnoC);
217    /// emitter.emit(EdifactEvent::start("NAD"))?;
218    /// emitter.emit(EdifactEvent::element("Müller"))?;
219    /// emitter.emit(EdifactEvent::EndSegment)?;
220    /// assert_eq!(emitter.finish()?, b"NAD+M\xFCller'".to_vec());
221    /// # Ok::<(), edifact_rs::EdifactError>(())
222    /// ```
223    #[must_use]
224    pub fn with_charset(mut self, charset: crate::Charset) -> Self {
225        self.writer = self.writer.with_charset(charset);
226        self
227    }
228
229    /// Flush and consume the emitter, returning the underlying writer.
230    pub fn finish(self) -> Result<W, EdifactError> {
231        self.writer.finish()
232    }
233
234    /// Number of complete segments written so far.
235    pub fn segment_count(&self) -> u64 {
236        self.writer.segment_count()
237    }
238
239    /// Return the active [`ServiceStringAdvice`][crate::ServiceStringAdvice].
240    ///
241    /// Callers can use this to format values (e.g., floats) using the correct
242    /// decimal-mark character configured in the UNA header.
243    pub fn service_string_advice(&self) -> crate::tokenizer::ServiceStringAdvice {
244        self.writer.service_string_advice()
245    }
246}
247
248impl<W: Write> EventEmitter for WriterEmitter<W> {
249    #[inline]
250    fn decimal_mark(&self) -> u8 {
251        self.writer.service_string_advice().decimal_mark
252    }
253
254    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
255        match event {
256            EdifactEvent::StartSegment { tag } => {
257                if self.state != EmitterState::Idle {
258                    return Err(EdifactError::InvalidEventSequence {
259                        message: "StartSegment emitted while a segment is already open; emit EndSegment first",
260                    });
261                }
262                self.state = EmitterState::InSegment;
263                self.writer.write_tag_only(&tag)?;
264            }
265            EdifactEvent::Element { value } => {
266                if self.state == EmitterState::Idle {
267                    return Err(EdifactError::InvalidEventSequence {
268                        message: "Element emitted outside of a segment; emit StartSegment first",
269                    });
270                }
271                self.state = EmitterState::InElement;
272                self.writer.write_element_sep()?;
273                self.writer.write_escaped(&value)?;
274            }
275            EdifactEvent::ComponentElement { value } => {
276                if self.state != EmitterState::InElement {
277                    return Err(EdifactError::InvalidEventSequence {
278                        message: "ComponentElement emitted without a preceding Element in the same segment",
279                    });
280                }
281                self.writer.write_component_sep()?;
282                self.writer.write_escaped(&value)?;
283            }
284            EdifactEvent::RepeatElement { value } => {
285                if self.state != EmitterState::InElement {
286                    return Err(EdifactError::InvalidEventSequence {
287                        message: "RepeatElement emitted without a preceding Element in the same segment",
288                    });
289                }
290                // Checked before the separator is written, so a rejected
291                // repetition leaves nothing half-emitted behind it.
292                self.writer.write_repetition_sep()?;
293                self.writer.write_escaped(&value)?;
294            }
295            EdifactEvent::EndSegment => {
296                if self.state == EmitterState::Idle {
297                    return Err(EdifactError::InvalidEventSequence {
298                        message: "EndSegment emitted while no segment is open; emit StartSegment first",
299                    });
300                }
301                self.state = EmitterState::Idle;
302                self.writer.write_segment_term_and_count()?;
303            }
304        }
305        Ok(())
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn vec_emitter_no_memory_leak() {
315        let mut e = VecEmitter::default();
316        e.emit(EdifactEvent::start("BGM")).unwrap();
317        e.emit(EdifactEvent::element("E03")).unwrap();
318        e.emit(EdifactEvent::EndSegment).unwrap();
319        assert_eq!(e.events[0], EdifactEvent::start("BGM".to_owned()));
320        assert_eq!(e.events[1], EdifactEvent::element("E03".to_owned()));
321    }
322
323    #[test]
324    fn writer_emitter_produces_valid_edifact() {
325        let mut buf = Vec::new();
326        {
327            let mut e = WriterEmitter::new(&mut buf);
328            e.emit(EdifactEvent::start("BGM")).unwrap();
329            e.emit(EdifactEvent::element("E03")).unwrap();
330            e.emit(EdifactEvent::element("11042")).unwrap();
331            e.emit(EdifactEvent::EndSegment).unwrap();
332            e.finish().unwrap();
333        }
334        assert_eq!(buf, b"BGM+E03+11042'");
335    }
336
337    #[test]
338    fn writer_emitter_handles_components() {
339        let mut buf = Vec::new();
340        {
341            let mut e = WriterEmitter::new(&mut buf);
342            e.emit(EdifactEvent::start("NAD")).unwrap();
343            e.emit(EdifactEvent::element("MS")).unwrap();
344            e.emit(EdifactEvent::element("9900112233445")).unwrap();
345            e.emit(EdifactEvent::component("")).unwrap();
346            e.emit(EdifactEvent::component("293")).unwrap();
347            e.emit(EdifactEvent::EndSegment).unwrap();
348            e.finish().unwrap();
349        }
350        let s = std::str::from_utf8(&buf).unwrap();
351        assert_eq!(s, "NAD+MS+9900112233445::293'");
352    }
353
354    #[test]
355    fn repetitions_round_trip_through_the_event_layer() {
356        // The parser splits repeating data elements, so the serializer has to be
357        // able to produce them — otherwise a value that goes out through a typed
358        // struct comes back collapsed into one occurrence.
359        let ssa = crate::ServiceStringAdvice::from_bytes(b"UNA:+.?*'").unwrap();
360        let mut buf = Vec::new();
361        {
362            let mut e = WriterEmitter::with_una(&mut buf, ssa).unwrap();
363            e.emit(EdifactEvent::start("RFF")).unwrap();
364            e.emit(EdifactEvent::element("ON")).unwrap();
365            e.emit(EdifactEvent::component("1")).unwrap();
366            e.emit(EdifactEvent::repeat("ON")).unwrap();
367            e.emit(EdifactEvent::component("2")).unwrap();
368            e.emit(EdifactEvent::EndSegment).unwrap();
369            e.finish().unwrap();
370        }
371        assert_eq!(
372            std::str::from_utf8(&buf).unwrap(),
373            "UNA:+.?*'RFF+ON:1*ON:2'"
374        );
375
376        let segments: Vec<_> = crate::from_bytes(&buf)
377            .collect::<Result<Vec<_>, _>>()
378            .unwrap();
379        let element = segments[0].get_element(0).unwrap();
380        assert_eq!(element.repeat_count(), 2);
381        assert_eq!(element.repetition(1).unwrap()[1].0, "2");
382    }
383
384    #[test]
385    fn a_repetition_without_a_declared_separator_is_refused() {
386        let mut e = WriterEmitter::new(Vec::<u8>::new());
387        e.emit(EdifactEvent::start("RFF")).unwrap();
388        e.emit(EdifactEvent::element("ON")).unwrap();
389        let err = e.emit(EdifactEvent::repeat("ON")).unwrap_err();
390        assert!(
391            matches!(err, EdifactError::RepetitionSeparatorNotDeclared),
392            "expected RepetitionSeparatorNotDeclared, got {err:?}"
393        );
394    }
395
396    #[test]
397    fn a_repetition_before_any_element_is_refused() {
398        let ssa = crate::ServiceStringAdvice::from_bytes(b"UNA:+.?*'").unwrap();
399        let mut e = WriterEmitter::with_una(Vec::<u8>::new(), ssa).unwrap();
400        e.emit(EdifactEvent::start("RFF")).unwrap();
401        let err = e.emit(EdifactEvent::repeat("ON")).unwrap_err();
402        assert!(
403            matches!(err, EdifactError::InvalidEventSequence { .. }),
404            "expected InvalidEventSequence, got {err:?}"
405        );
406    }
407
408    // ── protocol-violation tests (BUG 2.1) ───────────────────────────────────
409
410    #[test]
411    fn writer_emitter_element_before_start_segment_is_err() {
412        let mut e = WriterEmitter::new(Vec::<u8>::new());
413        let err = e.emit(EdifactEvent::element("X")).unwrap_err();
414        assert!(
415            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
416            "expected InvalidEventSequence, got {err:?}"
417        );
418    }
419
420    #[test]
421    fn writer_emitter_component_before_element_is_err() {
422        let mut e = WriterEmitter::new(Vec::<u8>::new());
423        e.emit(EdifactEvent::start("BGM")).unwrap();
424        let err = e.emit(EdifactEvent::component("X")).unwrap_err();
425        assert!(
426            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
427            "expected InvalidEventSequence, got {err:?}"
428        );
429    }
430
431    #[test]
432    fn writer_emitter_double_start_segment_is_err() {
433        let mut e = WriterEmitter::new(Vec::<u8>::new());
434        e.emit(EdifactEvent::start("BGM")).unwrap();
435        let err = e.emit(EdifactEvent::start("DTM")).unwrap_err();
436        assert!(
437            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
438            "expected InvalidEventSequence, got {err:?}"
439        );
440    }
441
442    #[test]
443    fn writer_emitter_end_segment_without_start_is_err() {
444        let mut e = WriterEmitter::new(Vec::<u8>::new());
445        let err = e.emit(EdifactEvent::EndSegment).unwrap_err();
446        assert!(
447            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
448            "expected InvalidEventSequence, got {err:?}"
449        );
450    }
451}