edifact-rs 0.16.0

Zero-copy EDIFACT parser, writer, serde traits, and extensible validation support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
//! Event model for EDIFACT (de)serialization.
//!
//! [`EdifactEvent`] is the borrowed, zero-allocation form used during real-time
//! emission.  [`OwnedEdifactEvent`] is the owned form collected by [`VecEmitter`]
//! for testing and introspection — no `Box::leak` anywhere.

use crate::EdifactError;
use std::io::Write;

// ── event types ───────────────────────────────────────────────────────────────

/// A borrowed EDIFACT event emitted during serialization.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EdifactEvent<'a> {
    /// Beginning of a new segment (e.g. `"BGM"`, `"NAD"`).
    StartSegment {
        /// Segment tag.
        tag: &'a str,
    },
    /// A data element value — first (or only) component of a new element.
    Element {
        /// Element text value.
        value: &'a str,
    },
    /// An additional component within the current element.
    ComponentElement {
        /// Component text value.
        value: &'a str,
    },
    /// The first component of a further occurrence of the current data element.
    ///
    /// The write-side mirror of [`Token::RepeatElement`][crate::Token::RepeatElement]:
    /// the parser splits repeating data elements, so the serializer has to be
    /// able to produce them, or a value that round-trips through a typed struct
    /// comes back collapsed into one occurrence.
    ///
    /// Requires an active repetition separator — see
    /// [`ServiceStringAdvice::is_repetition_active`][crate::ServiceStringAdvice::is_repetition_active].
    /// Without one there is no byte to separate occurrences with, so
    /// [`WriterEmitter`] returns
    /// [`EdifactError::RepetitionSeparatorNotDeclared`] rather than emitting
    /// output that reads back as a single occurrence.
    RepeatElement {
        /// First component of the new occurrence.
        value: &'a str,
    },
    /// End of the current segment.
    EndSegment,
}

/// An owned EDIFACT event — for collection and testing (no borrowed lifetimes).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OwnedEdifactEvent {
    /// Owned segment-start event.
    StartSegment {
        /// Segment tag.
        tag: String,
    },
    /// Owned element event.
    Element {
        /// Element text value.
        value: String,
    },
    /// Owned component event.
    ComponentElement {
        /// Component text value.
        value: String,
    },
    /// Owned repetition event.
    RepeatElement {
        /// First component of the new occurrence.
        value: String,
    },
    /// Owned segment-end event.
    EndSegment,
}

impl EdifactEvent<'_> {
    /// Convert to an owned event, cloning string data.
    pub fn into_owned(self) -> OwnedEdifactEvent {
        match self {
            Self::StartSegment { tag } => OwnedEdifactEvent::StartSegment {
                tag: tag.to_owned(),
            },
            Self::Element { value } => OwnedEdifactEvent::Element {
                value: value.to_owned(),
            },
            Self::ComponentElement { value } => OwnedEdifactEvent::ComponentElement {
                value: value.to_owned(),
            },
            Self::RepeatElement { value } => OwnedEdifactEvent::RepeatElement {
                value: value.to_owned(),
            },
            Self::EndSegment => OwnedEdifactEvent::EndSegment,
        }
    }
}

// ── emitter trait ─────────────────────────────────────────────────────────────

/// Trait for any sink that can consume [`EdifactEvent`]s.
pub trait EventEmitter {
    /// Consume one event.
    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError>;

    /// Return the decimal-mark byte used by the interchange (`b'.'` by default).
    ///
    /// Serializers that format numeric values (e.g. [`crate::ser::DecimalFloat`])
    /// call this to discover whether to emit `12.5` or `12,5`.
    ///
    /// The default implementation returns `b'.'`, which is correct for standard
    /// EDIFACT interchanges that do not declare a UNA service string or that use
    /// the ISO 9735 default.  Override this in emitters backed by a
    /// [`crate::Writer`] with a custom [`crate::ServiceStringAdvice`].
    #[inline]
    fn decimal_mark(&self) -> u8 {
        b'.'
    }
}

// ── VecEmitter ────────────────────────────────────────────────────────────────

/// Collects events into a [`Vec<OwnedEdifactEvent>`].
///
/// Useful for testing and introspection.  Does not leak memory.
#[derive(Debug, Default)]
pub struct VecEmitter {
    /// Collected owned events.
    pub events: Vec<OwnedEdifactEvent>,
}

impl EventEmitter for VecEmitter {
    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
        self.events.push(event.into_owned());
        Ok(())
    }
}

// ── WriterEmitter ─────────────────────────────────────────────────────────────

/// Internal protocol-state machine for [`WriterEmitter`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EmitterState {
    /// Between segments: no open segment.
    Idle,
    /// A [`EdifactEvent::StartSegment`] has been emitted; no element written yet.
    InSegment,
    /// An [`EdifactEvent::Element`] has been emitted; `ComponentElement` is valid.
    InElement,
}

/// Writes EDIFACT events directly to any [`Write`] implementation.
///
/// Each event is written to the underlying writer immediately — no intermediate
/// buffering of element strings occurs, so no heap allocation is required per
/// event.  This makes `WriterEmitter` suitable for high-throughput serialization
/// of large EDIFACT messages.
///
/// # Protocol
///
/// Events must arrive in the order produced by [`crate::EdifactSerialize`]:
/// `StartSegment` → zero or more (`Element` → zero or more `ComponentElement`) → `EndSegment`.
///
/// Any violation of this protocol returns
/// [`EdifactError::InvalidEventSequence`] immediately.  Violations are
/// detected in both debug and release builds.
pub struct WriterEmitter<W: Write> {
    writer: crate::Writer<W>,
    state: EmitterState,
}

impl<W: Write> WriterEmitter<W> {
    /// Create a new `WriterEmitter` with default EDIFACT delimiters.
    pub fn new(inner: W) -> Self {
        Self {
            writer: crate::Writer::new(inner),
            state: EmitterState::Idle,
        }
    }

    /// Create a new `WriterEmitter` with custom delimiters, writing a UNA header first.
    ///
    /// # Errors
    ///
    /// Returns [`EdifactError::InvalidUna`] when `ssa.is_valid()` is false.
    pub fn with_una(
        inner: W,
        ssa: crate::tokenizer::ServiceStringAdvice,
    ) -> Result<Self, crate::EdifactError> {
        Ok(Self {
            writer: crate::Writer::with_una(inner, ssa)?,
            state: EmitterState::Idle,
        })
    }

    /// Bind this emitter's writer to a character repertoire.
    ///
    /// The typed serialization path had no way to reach
    /// [`Writer::with_charset`][crate::Writer::with_charset], so a `#[derive(EdifactSerialize)]`
    /// struct could only ever go out as UTF-8 — which is wrong for every
    /// `UNOC`…`UNOK` partner, and wrong in the silent way: `ü` arrives as two
    /// mojibake characters rather than as an error.
    ///
    /// # Example
    ///
    /// ```
    /// use edifact_rs::{Charset, EdifactEvent, EventEmitter, WriterEmitter};
    ///
    /// let mut emitter = WriterEmitter::new(Vec::new()).with_charset(Charset::UnoC);
    /// emitter.emit(EdifactEvent::StartSegment { tag: "NAD" })?;
    /// emitter.emit(EdifactEvent::Element { value: "Müller" })?;
    /// emitter.emit(EdifactEvent::EndSegment)?;
    /// assert_eq!(emitter.finish()?, b"NAD+M\xFCller'".to_vec());
    /// # Ok::<(), edifact_rs::EdifactError>(())
    /// ```
    #[must_use]
    pub fn with_charset(mut self, charset: crate::Charset) -> Self {
        self.writer = self.writer.with_charset(charset);
        self
    }

    /// Flush and consume the emitter, returning the underlying writer.
    pub fn finish(self) -> Result<W, EdifactError> {
        self.writer.finish()
    }

    /// Number of complete segments written so far.
    pub fn segment_count(&self) -> u64 {
        self.writer.segment_count()
    }

    /// Return the active [`ServiceStringAdvice`][crate::ServiceStringAdvice].
    ///
    /// Callers can use this to format values (e.g., floats) using the correct
    /// decimal-mark character configured in the UNA header.
    pub fn service_string_advice(&self) -> crate::tokenizer::ServiceStringAdvice {
        self.writer.service_string_advice()
    }
}

impl<W: Write> EventEmitter for WriterEmitter<W> {
    #[inline]
    fn decimal_mark(&self) -> u8 {
        self.writer.service_string_advice().decimal_mark
    }

    fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
        match event {
            EdifactEvent::StartSegment { tag } => {
                if self.state != EmitterState::Idle {
                    return Err(EdifactError::InvalidEventSequence {
                        message: "StartSegment emitted while a segment is already open; emit EndSegment first",
                    });
                }
                self.state = EmitterState::InSegment;
                self.writer.write_tag_only(tag)?;
            }
            EdifactEvent::Element { value } => {
                if self.state == EmitterState::Idle {
                    return Err(EdifactError::InvalidEventSequence {
                        message: "Element emitted outside of a segment; emit StartSegment first",
                    });
                }
                self.state = EmitterState::InElement;
                self.writer.write_element_sep()?;
                self.writer.write_escaped(value)?;
            }
            EdifactEvent::ComponentElement { value } => {
                if self.state != EmitterState::InElement {
                    return Err(EdifactError::InvalidEventSequence {
                        message: "ComponentElement emitted without a preceding Element in the same segment",
                    });
                }
                self.writer.write_component_sep()?;
                self.writer.write_escaped(value)?;
            }
            EdifactEvent::RepeatElement { value } => {
                if self.state != EmitterState::InElement {
                    return Err(EdifactError::InvalidEventSequence {
                        message: "RepeatElement emitted without a preceding Element in the same segment",
                    });
                }
                // Checked before the separator is written, so a rejected
                // repetition leaves nothing half-emitted behind it.
                self.writer.write_repetition_sep()?;
                self.writer.write_escaped(value)?;
            }
            EdifactEvent::EndSegment => {
                if self.state == EmitterState::Idle {
                    return Err(EdifactError::InvalidEventSequence {
                        message: "EndSegment emitted while no segment is open; emit StartSegment first",
                    });
                }
                self.state = EmitterState::Idle;
                self.writer.write_segment_term_and_count()?;
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn vec_emitter_no_memory_leak() {
        let mut e = VecEmitter::default();
        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
        e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
        e.emit(EdifactEvent::EndSegment).unwrap();
        assert_eq!(
            e.events[0],
            OwnedEdifactEvent::StartSegment {
                tag: "BGM".to_owned()
            }
        );
        assert_eq!(
            e.events[1],
            OwnedEdifactEvent::Element {
                value: "E03".to_owned()
            }
        );
    }

    #[test]
    fn writer_emitter_produces_valid_edifact() {
        let mut buf = Vec::new();
        {
            let mut e = WriterEmitter::new(&mut buf);
            e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
            e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
            e.emit(EdifactEvent::Element { value: "11042" }).unwrap();
            e.emit(EdifactEvent::EndSegment).unwrap();
            e.finish().unwrap();
        }
        assert_eq!(buf, b"BGM+E03+11042'");
    }

    #[test]
    fn writer_emitter_handles_components() {
        let mut buf = Vec::new();
        {
            let mut e = WriterEmitter::new(&mut buf);
            e.emit(EdifactEvent::StartSegment { tag: "NAD" }).unwrap();
            e.emit(EdifactEvent::Element { value: "MS" }).unwrap();
            e.emit(EdifactEvent::Element {
                value: "9900112233445",
            })
            .unwrap();
            e.emit(EdifactEvent::ComponentElement { value: "" })
                .unwrap();
            e.emit(EdifactEvent::ComponentElement { value: "293" })
                .unwrap();
            e.emit(EdifactEvent::EndSegment).unwrap();
            e.finish().unwrap();
        }
        let s = std::str::from_utf8(&buf).unwrap();
        assert_eq!(s, "NAD+MS+9900112233445::293'");
    }

    #[test]
    fn repetitions_round_trip_through_the_event_layer() {
        // The parser splits repeating data elements, so the serializer has to be
        // able to produce them — otherwise a value that goes out through a typed
        // struct comes back collapsed into one occurrence.
        let ssa = crate::ServiceStringAdvice::from_bytes(b"UNA:+.?*'").unwrap();
        let mut buf = Vec::new();
        {
            let mut e = WriterEmitter::with_una(&mut buf, ssa).unwrap();
            e.emit(EdifactEvent::StartSegment { tag: "RFF" }).unwrap();
            e.emit(EdifactEvent::Element { value: "ON" }).unwrap();
            e.emit(EdifactEvent::ComponentElement { value: "1" })
                .unwrap();
            e.emit(EdifactEvent::RepeatElement { value: "ON" }).unwrap();
            e.emit(EdifactEvent::ComponentElement { value: "2" })
                .unwrap();
            e.emit(EdifactEvent::EndSegment).unwrap();
            e.finish().unwrap();
        }
        assert_eq!(
            std::str::from_utf8(&buf).unwrap(),
            "UNA:+.?*'RFF+ON:1*ON:2'"
        );

        let segments: Vec<_> = crate::from_bytes(&buf)
            .collect::<Result<Vec<_>, _>>()
            .unwrap();
        let element = segments[0].get_element(0).unwrap();
        assert_eq!(element.repeat_count(), 2);
        assert_eq!(element.repetition(1).unwrap()[1].0, "2");
    }

    #[test]
    fn a_repetition_without_a_declared_separator_is_refused() {
        let mut e = WriterEmitter::new(Vec::<u8>::new());
        e.emit(EdifactEvent::StartSegment { tag: "RFF" }).unwrap();
        e.emit(EdifactEvent::Element { value: "ON" }).unwrap();
        let err = e
            .emit(EdifactEvent::RepeatElement { value: "ON" })
            .unwrap_err();
        assert!(
            matches!(err, EdifactError::RepetitionSeparatorNotDeclared),
            "expected RepetitionSeparatorNotDeclared, got {err:?}"
        );
    }

    #[test]
    fn a_repetition_before_any_element_is_refused() {
        let ssa = crate::ServiceStringAdvice::from_bytes(b"UNA:+.?*'").unwrap();
        let mut e = WriterEmitter::with_una(Vec::<u8>::new(), ssa).unwrap();
        e.emit(EdifactEvent::StartSegment { tag: "RFF" }).unwrap();
        let err = e
            .emit(EdifactEvent::RepeatElement { value: "ON" })
            .unwrap_err();
        assert!(
            matches!(err, EdifactError::InvalidEventSequence { .. }),
            "expected InvalidEventSequence, got {err:?}"
        );
    }

    // ── protocol-violation tests (BUG 2.1) ───────────────────────────────────

    #[test]
    fn writer_emitter_element_before_start_segment_is_err() {
        let mut e = WriterEmitter::new(Vec::<u8>::new());
        let err = e.emit(EdifactEvent::Element { value: "X" }).unwrap_err();
        assert!(
            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
            "expected InvalidEventSequence, got {err:?}"
        );
    }

    #[test]
    fn writer_emitter_component_before_element_is_err() {
        let mut e = WriterEmitter::new(Vec::<u8>::new());
        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
        let err = e
            .emit(EdifactEvent::ComponentElement { value: "X" })
            .unwrap_err();
        assert!(
            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
            "expected InvalidEventSequence, got {err:?}"
        );
    }

    #[test]
    fn writer_emitter_double_start_segment_is_err() {
        let mut e = WriterEmitter::new(Vec::<u8>::new());
        e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
        let err = e
            .emit(EdifactEvent::StartSegment { tag: "DTM" })
            .unwrap_err();
        assert!(
            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
            "expected InvalidEventSequence, got {err:?}"
        );
    }

    #[test]
    fn writer_emitter_end_segment_without_start_is_err() {
        let mut e = WriterEmitter::new(Vec::<u8>::new());
        let err = e.emit(EdifactEvent::EndSegment).unwrap_err();
        assert!(
            matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
            "expected InvalidEventSequence, got {err:?}"
        );
    }
}