Skip to main content

edifact_rs/
de.rs

1//! Typed deserialization: EDIFACT segments to Rust values.
2//!
3//! [`EdifactDeserialize`] maps a slice of parsed [`Segment`]s onto a struct.
4//! Implement it by hand or, far more usually, derive it — see
5//! [`EdifactDeserialize`][macro@crate::EdifactDeserialize].
6//!
7//! [`EdifactSegmentTag`] is the companion trait that carries a type's segment
8//! tag (and optional qualifier pattern), which is what makes the blanket
9//! `impl EdifactDeserialize for Vec<T>` and the streaming helpers possible.
10//!
11//! # Entry points
12//!
13//! | Function | Input | Yields |
14//! |---|---|---|
15//! | [`deserialize`] | `&[u8]` | one `T` |
16//! | [`deserialize_str`] | `&str` | one `T` |
17//! | [`deserialize_each`] | `&[u8]` | every matching `T`, lazily |
18//! | [`deserialize_each_from_reader`] | `impl Read` | every matching `T`, lazily |
19//! | [`deserialize_messages`] | `&[u8]` | one `T` per `UNH`/`UNT` message |
20//! | [`deserialize_messages_from_reader`] | `impl Read` | one `T` per message |
21
22use crate::{EdifactError, Segment};
23use std::borrow::Cow;
24use std::io::Read;
25
26// ── traits ────────────────────────────────────────────────────────────────────
27
28/// Types that can be deserialized from a slice of EDIFACT segments.
29///
30/// Implement manually or derive with
31/// [`#[derive(EdifactDeserialize)]`][macro@crate::EdifactDeserialize].
32///
33/// The slice may hold any number of segments; an implementation extracts the
34/// ones it cares about and ignores the rest. Both parsing paths are covered by
35/// the one signature: `&[OwnedSegment]` coerces to `&[Segment<'_>]`.
36pub trait EdifactDeserialize: Sized {
37    /// Deserialize `Self` from the provided segment slice.
38    ///
39    /// # Errors
40    ///
41    /// Implementation-defined; derived impls report
42    /// [`EdifactError::MissingSegment`] for an absent segment,
43    /// [`EdifactError::MissingRequiredElement`] for an absent mandatory field,
44    /// and [`EdifactError::InvalidFieldValue`] for one that will not parse.
45    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError>;
46}
47
48/// Types that can be deserialized from a composite EDIFACT element.
49///
50/// Implement this for custom composite structs used with
51/// `#[edifact(composite)]` in derive macros.
52pub trait EdifactCompositeDeserialize: Sized {
53    /// Deserialize `Self` from a composite element.
54    ///
55    /// # Errors
56    ///
57    /// Implementation-defined; typically
58    /// [`EdifactError::MissingRequiredComponent`].
59    fn edifact_deserialize_composite(composite: CompositeElement<'_>)
60    -> Result<Self, EdifactError>;
61}
62
63impl EdifactCompositeDeserialize for Vec<String> {
64    fn edifact_deserialize_composite(
65        composite: CompositeElement<'_>,
66    ) -> Result<Self, EdifactError> {
67        Ok(composite.iter().map(str::to_owned).collect())
68    }
69}
70
71/// Companion trait that declares a type's segment tag (and optional qualifier).
72///
73/// Required for the `Vec<T>` blanket impl and for finding the right segment in
74/// a message-level struct deserialization.
75pub trait EdifactSegmentTag {
76    /// The 3-character EDIFACT segment tag (e.g. `"BGM"`, `"NAD"`).
77    const SEGMENT_TAG: &'static str;
78
79    /// Optional qualifier pattern to further constrain segment matching.
80    ///
81    /// Examples:
82    /// - `Some("MS")` for exact qualifier matching.
83    /// - `Some("M*")` for wildcard prefix matching (matches `"MS"`, `"MR"`, etc.).
84    const QUALIFIER_PATTERN: Option<&'static str> = None;
85
86    /// Return `true` if `seg`'s qualifier matches this type's qualifier pattern.
87    fn matches_qualifier(seg: &Segment<'_>) -> bool {
88        match Self::QUALIFIER_PATTERN {
89            Some(pattern) => qualifier_matches_pattern(seg.element_str(0).unwrap_or(""), pattern),
90            None => true,
91        }
92    }
93
94    /// Return `true` if `seg` is the segment this type maps to.
95    ///
96    /// Default: the tag matches and, when a qualifier pattern is declared,
97    /// element 0 matches it (e.g. `NAD+BY`).
98    fn matches_segment(seg: &Segment<'_>) -> bool {
99        seg.tag == Self::SEGMENT_TAG && Self::matches_qualifier(seg)
100    }
101}
102
103// ── blanket impl for Vec<T> ───────────────────────────────────────────────────
104
105/// Deserializes each segment matching `T::matches_segment` as an independent
106/// single-segment slice, collecting the results.
107impl<T> EdifactDeserialize for Vec<T>
108where
109    T: EdifactDeserialize + EdifactSegmentTag,
110{
111    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
112        segments
113            .iter()
114            .filter(|s| T::matches_segment(s))
115            .map(|seg| T::edifact_deserialize(std::slice::from_ref(seg)))
116            .collect()
117    }
118}
119
120// ── entry points ──────────────────────────────────────────────────────────────
121
122/// Deserialize a value of type `T` from EDIFACT bytes.
123///
124/// Buffers every parsed segment into a `Vec<Segment<'_>>` before handing it to
125/// `T`. For large interchanges prefer [`deserialize_each`] (one segment at a
126/// time) or [`deserialize_messages`] (one message at a time).
127///
128/// # Errors
129///
130/// Any parse error, or whatever `T` reports.
131pub fn deserialize<T: EdifactDeserialize>(input: &[u8]) -> Result<T, EdifactError> {
132    let segments: Vec<Segment<'_>> = crate::from_bytes(input).collect::<Result<_, _>>()?;
133    T::edifact_deserialize(&segments)
134}
135
136/// Deserialize a value of type `T` from an EDIFACT string.
137///
138/// # Errors
139///
140/// As [`deserialize`].
141pub fn deserialize_str<T: EdifactDeserialize>(input: &str) -> Result<T, EdifactError> {
142    deserialize(input.as_bytes())
143}
144
145/// Lazily deserialize every segment in `input` that `T` maps to.
146///
147/// Non-matching segments are never buffered, so memory stays proportional to one
148/// segment rather than the whole interchange. Take the first match with
149/// `.next()`; collect them all with `.collect::<Result<Vec<_>, _>>()`.
150///
151/// # Example
152///
153#[cfg_attr(feature = "derive", doc = "```")]
154#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
155/// use edifact_rs::{EdifactDeserialize, deserialize_each};
156///
157/// #[derive(EdifactDeserialize)]
158/// #[edifact(segment = "RFF")]
159/// struct Rff {
160///     #[edifact(element = 0, component = 1)]
161///     number: String,
162/// }
163///
164/// let input = b"UNH+1+ORDERS:D:96A:UN'RFF+ON:A'BGM+220'RFF+ON:B'UNT+5+1'";
165/// let refs: Vec<Rff> = deserialize_each(input).collect::<Result<_, _>>()?;
166///
167/// assert_eq!(refs.len(), 2);
168/// assert_eq!(refs[1].number, "B");
169/// # Ok::<(), edifact_rs::EdifactError>(())
170/// ```
171pub fn deserialize_each<'a, T>(
172    input: &'a [u8],
173) -> impl Iterator<Item = Result<T, EdifactError>> + 'a
174where
175    T: EdifactDeserialize + EdifactSegmentTag + 'a,
176{
177    deserialize_matching(crate::from_bytes(input))
178}
179
180/// Lazily deserialize every segment from a reader that `T` maps to.
181///
182/// The reader counterpart of [`deserialize_each`], with the same bounded-memory
183/// behaviour.
184pub fn deserialize_each_from_reader<T, R>(
185    reader: R,
186) -> impl Iterator<Item = Result<T, EdifactError>>
187where
188    T: EdifactDeserialize + EdifactSegmentTag,
189    R: Read,
190{
191    deserialize_matching(crate::from_reader(reader))
192}
193
194/// Shared driver behind [`deserialize_each`] and [`deserialize_each_from_reader`].
195fn deserialize_matching<'a, T, I>(segments: I) -> impl Iterator<Item = Result<T, EdifactError>>
196where
197    T: EdifactDeserialize + EdifactSegmentTag,
198    I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
199{
200    segments.filter_map(|segment| match segment {
201        Ok(segment) if T::matches_segment(&segment) => {
202            Some(T::edifact_deserialize(std::slice::from_ref(&segment)))
203        }
204        Ok(_) => None,
205        Err(error) => Some(Err(error)),
206    })
207}
208
209// ── segment lookup ────────────────────────────────────────────────────────────
210
211/// Find the first segment with the given tag.
212pub fn find_segment<'s, 'd>(segments: &'s [Segment<'d>], tag: &str) -> Option<&'s Segment<'d>> {
213    segments.iter().find(|s| s.tag == tag)
214}
215
216/// Iterate over all segments with the given tag.
217pub fn find_segments<'s, 'd: 's>(
218    segments: &'s [Segment<'d>],
219    tag: &'s str,
220) -> impl Iterator<Item = &'s Segment<'d>> {
221    segments.iter().filter(move |s| s.tag == tag)
222}
223
224/// Find the first segment matching `tag` whose element 0 equals `qualifier`.
225pub fn find_qualified_segment<'s, 'd>(
226    segments: &'s [Segment<'d>],
227    tag: &str,
228    qualifier: &str,
229) -> Option<&'s Segment<'d>> {
230    segments
231        .iter()
232        .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
233}
234
235/// Iterate over every segment that the type `T` maps to (tag plus qualifier pattern).
236pub fn find_segments_typed<'s, 'd: 's, T>(
237    segments: &'s [Segment<'d>],
238) -> impl Iterator<Item = &'s Segment<'d>>
239where
240    T: EdifactSegmentTag,
241{
242    segments.iter().filter(|s| T::matches_segment(s))
243}
244
245/// Iterate lazily over contiguous runs of segments that `T` maps to.
246///
247/// Each item is a borrowed sub-slice of `segments` covering one uninterrupted
248/// run of matches — the shape a repeating segment group has on the wire.
249///
250/// # Example
251///
252/// ```
253/// use edifact_rs::{EdifactSegmentTag, contiguous_groups, from_bytes};
254///
255/// struct Loc;
256/// impl EdifactSegmentTag for Loc {
257///     const SEGMENT_TAG: &'static str = "LOC";
258/// }
259///
260/// let segments: Vec<_> = from_bytes(b"LOC+1'LOC+2'DTM+137'LOC+3'")
261///     .collect::<Result<Vec<_>, _>>()?;
262/// let runs: Vec<usize> = contiguous_groups::<Loc>(&segments).map(<[_]>::len).collect();
263///
264/// assert_eq!(runs, [2, 1]);
265/// # Ok::<(), edifact_rs::EdifactError>(())
266/// ```
267pub fn contiguous_groups<'s, 'd, T>(
268    segments: &'s [Segment<'d>],
269) -> impl Iterator<Item = &'s [Segment<'d>]> + 's
270where
271    T: EdifactSegmentTag,
272{
273    let mut idx = 0;
274    let len = segments.len();
275    std::iter::from_fn(move || {
276        while idx < len && !T::matches_segment(&segments[idx]) {
277            idx += 1;
278        }
279        if idx >= len {
280            return None;
281        }
282        let start = idx;
283        idx += 1;
284        while idx < len && T::matches_segment(&segments[idx]) {
285            idx += 1;
286        }
287        Some(&segments[start..idx])
288    })
289}
290
291/// Match a qualifier value against an exact or wildcard pattern.
292///
293/// Rules:
294/// - If `pattern` contains `*`, it is treated as a glob wildcard (e.g. `"M*"` matches `"MS"`, `"MR"`).
295/// - If no wildcard is present, exact match is required.
296///
297/// Prefix matching without an explicit `*` is deliberately *not* supported: `"M"`
298/// matches only `"M"`, not `"MS"`. Use `"M*"` for prefix semantics.
299///
300/// Patterns with more than three wildcard-separated gaps (four or more `*`) are
301/// rejected outright, guarding against pathological O(n·m) matching.
302pub fn qualifier_matches_pattern(value: &str, pattern: &str) -> bool {
303    if pattern.is_empty() {
304        return value.is_empty();
305    }
306
307    if !pattern.contains('*') {
308        return value == pattern;
309    }
310
311    // Fast path: single wildcard (dominant case — e.g. "M*" or "*:MS").
312    // The length test is what stops the prefix and the suffix from overlapping:
313    // `value.len() >= prefix.len() + suffix.len()` is exactly the condition that
314    // leaves a (possibly empty) gap between them for `*` to cover.
315    if let Some((prefix, suffix)) = pattern.split_once('*') {
316        if !suffix.contains('*') {
317            return value.len() >= prefix.len() + suffix.len()
318                && value.starts_with(prefix)
319                && value.ends_with(suffix);
320        }
321    }
322
323    // General multi-wildcard path.
324    let parts: smallvec::SmallVec<[&str; 4]> = pattern.split('*').collect();
325
326    // EDIFACT qualifier patterns use at most one or two wildcards; four is a
327    // generous ceiling.  Anything beyond is a programming error or adversarial
328    // input — reject immediately rather than backtrack.
329    if parts.len() > 4 {
330        return false;
331    }
332
333    let prefix = parts[0];
334    let suffix = parts[parts.len() - 1];
335
336    if !value.starts_with(prefix) || !value.ends_with(suffix) {
337        return false;
338    }
339
340    let mid_start = prefix.len();
341    let mid_end = value.len().saturating_sub(suffix.len());
342
343    if mid_start > mid_end {
344        return parts[1..parts.len() - 1].iter().all(|p| p.is_empty());
345    }
346
347    let mut remaining = &value[mid_start..mid_end];
348
349    for part in &parts[1..parts.len() - 1] {
350        if part.is_empty() {
351            continue;
352        }
353        match remaining.find(part) {
354            Some(idx) => remaining = &remaining[idx + part.len()..],
355            None => return false,
356        }
357    }
358
359    true
360}
361
362// ── composite elements ────────────────────────────────────────────────────────
363
364/// A composite data element, flattened to its component strings.
365///
366/// Holds borrowed `&'a str` references to the underlying data — no string
367/// copies are made. Up to four component pointers are stored inline, so the
368/// common case is allocation-free.
369pub struct CompositeElement<'a> {
370    components: smallvec::SmallVec<[&'a str; 4]>,
371}
372
373impl<'a> CompositeElement<'a> {
374    /// Build a composite view over a slice of component values.
375    pub fn from_slice(components: &'a [Cow<'a, str>]) -> Self {
376        Self {
377            components: components.iter().map(|c| c.as_ref()).collect(),
378        }
379    }
380
381    /// Crate-private constructor for direct `&str` components.
382    pub(crate) fn from_strs(components: smallvec::SmallVec<[&'a str; 4]>) -> Self {
383        Self { components }
384    }
385
386    /// Get the component at index `i`, or `None` if absent.
387    pub fn get(&self, i: usize) -> Option<&'a str> {
388        self.components.get(i).copied()
389    }
390
391    /// Get the component at index `i`, or `""` if absent.
392    pub fn get_or_empty(&self, i: usize) -> &'a str {
393        self.get(i).unwrap_or("")
394    }
395
396    /// Number of components.
397    pub fn len(&self) -> usize {
398        self.components.len()
399    }
400
401    /// Returns `true` when the composite carries no components.
402    pub fn is_empty(&self) -> bool {
403        self.components.is_empty()
404    }
405
406    /// Iterate over all component values.
407    pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
408        self.components.iter().copied()
409    }
410}
411
412/// View element `idx` of `seg` as a [`CompositeElement`].
413pub fn composite_element<'a, 'd: 'a>(
414    seg: &'a Segment<'d>,
415    idx: usize,
416) -> Option<CompositeElement<'a>> {
417    seg.elements
418        .get(idx)
419        .map(|elem| CompositeElement::from_strs(elem.components().collect()))
420}
421
422// ── message-window streaming ──────────────────────────────────────────────────
423
424/// A complete `UNH..UNT` message, lifted out of an interchange.
425///
426/// Produced by [`message_windows`] and [`message_windows_from_reader`].
427/// `message_type` and `association_code` are read off the `UNH` at construction
428/// time, so routing logic never has to traverse `segments` itself.
429///
430/// `segments` holds the **full** window, `UNH` and `UNT` included, so that
431/// envelope-aware consumers can reach them; [`body`][Self::body] is the view
432/// without them.
433#[derive(Debug, Clone)]
434pub struct MessageWindow<'a> {
435    /// EDIFACT message type from `UNH` element 1, component 0 (DE 0065).
436    pub message_type: Option<Cow<'a, str>>,
437    /// Association-assigned code from `UNH` element 1, component 4 (DE 0057).
438    pub association_code: Option<Cow<'a, str>>,
439    /// All segments in this window, from `UNH` through `UNT` inclusive.
440    pub segments: Vec<Segment<'a>>,
441}
442
443/// A [`MessageWindow`] that owns its text — what the reader path produces.
444pub type OwnedMessageWindow = MessageWindow<'static>;
445
446impl<'a> MessageWindow<'a> {
447    /// The message **body**: everything between `UNH` and `UNT`, exclusive.
448    ///
449    /// [`segments`][Self::segments] deliberately includes the service segments so
450    /// that envelope-aware consumers can read them, but they are exactly what a
451    /// body-oriented pass does not want. In particular
452    /// [`group_segments_indexed`][crate::group_segments_indexed] is driven by
453    /// trigger tags alone, so a trailing `UNT` lands inside whichever group ran
454    /// last — pass `body()` and it cannot.
455    ///
456    /// Missing service segments are tolerated: a window that somehow lacks its
457    /// `UNH` or `UNT` yields whatever it does have, rather than panicking.
458    ///
459    /// # Example
460    ///
461    /// ```
462    /// use edifact_rs::message_windows;
463    ///
464    /// let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'UNT+3+1'";
465    /// let windows: Vec<_> = message_windows(input).collect::<Result<Vec<_>, _>>()?;
466    ///
467    /// assert_eq!(windows[0].segments.len(), 3); // UNH, BGM, UNT
468    /// assert_eq!(
469    ///     windows[0].body().iter().map(edifact_rs::Segment::tag).collect::<Vec<_>>(),
470    ///     ["BGM"],
471    /// );
472    /// # Ok::<(), edifact_rs::EdifactError>(())
473    /// ```
474    #[must_use]
475    pub fn body(&self) -> &[Segment<'a>] {
476        let start = usize::from(self.segments.first().is_some_and(|s| s.tag == "UNH"));
477        let end = self.segments.len().saturating_sub(usize::from(
478            self.segments.last().is_some_and(|s| s.tag == "UNT"),
479        ));
480        self.segments.get(start..end).unwrap_or(&[])
481    }
482
483    /// Build a window from a completed segment buffer, reading the `UNH` metadata.
484    fn from_segments(segments: Vec<Segment<'a>>) -> Self {
485        let unh = segments.first().filter(|s| s.tag == "UNH");
486        let component = |idx: usize| -> Option<Cow<'a, str>> {
487            unh?.elements
488                .get(1)?
489                .components
490                .get(idx)
491                .map(|(c, _)| c.clone())
492                .filter(|c| !c.is_empty())
493        };
494        Self {
495            message_type: component(0),
496            association_code: component(4),
497            segments,
498        }
499    }
500}
501
502/// Groups a segment stream into per-message `UNH..UNT` windows.
503///
504/// One iterator for both parsing paths: wrap [`from_bytes`][crate::from_bytes]
505/// and the windows borrow from the input; wrap
506/// [`from_reader`][crate::from_reader] and they own their text. Envelope
507/// segments outside any `UNH..UNT` pair are skipped.
508///
509/// # Errors
510///
511/// - An inner-iterator error is forwarded immediately and iteration stops.
512/// - A `UNH` seen while a prior window is still open (missing `UNT`) is an error.
513/// - Input that ends with a window still open yields
514///   [`EdifactError::UnexpectedEof`] before returning `None`, so a truncated
515///   stream can never be mistaken for a complete one.
516pub struct MessageWindows<'a, I> {
517    inner: I,
518    buf: Vec<Segment<'a>>,
519    in_message: bool,
520    /// Set after any terminal condition so later `next()` calls return `None`.
521    done: bool,
522}
523
524impl<'a, I> MessageWindows<'a, I>
525where
526    I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
527{
528    /// Wrap any segment iterator as a message-window iterator.
529    pub fn new(inner: I) -> Self {
530        Self {
531            inner,
532            buf: Vec::new(),
533            in_message: false,
534            done: false,
535        }
536    }
537}
538
539impl<'a, I> Iterator for MessageWindows<'a, I>
540where
541    I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
542{
543    type Item = Result<MessageWindow<'a>, EdifactError>;
544
545    fn next(&mut self) -> Option<Self::Item> {
546        if self.done {
547            return None;
548        }
549        loop {
550            let segment = match self.inner.next() {
551                Some(Ok(s)) => s,
552                Some(Err(e)) => {
553                    self.done = true;
554                    return Some(Err(e));
555                }
556                None => {
557                    self.done = true;
558                    // A window that opened but never closed means the stream was
559                    // truncated — surfacing it as an error is what stops a caller
560                    // from accepting a partial message as a whole one.
561                    if self.in_message && !self.buf.is_empty() {
562                        self.in_message = false;
563                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
564                        return Some(Err(EdifactError::UnexpectedEof { offset }));
565                    }
566                    return None;
567                }
568            };
569
570            match segment.tag() {
571                "UNH" => {
572                    if self.in_message {
573                        self.buf.clear();
574                        self.in_message = false;
575                        self.done = true;
576                        return Some(Err(EdifactError::InvalidSegmentForMessage {
577                            tag: "UNH".to_owned(),
578                            message_type: "ENVELOPE".to_owned(),
579                            span: segment.span,
580                        }));
581                    }
582                    self.buf.clear();
583                    self.in_message = true;
584                    self.buf.push(segment);
585                }
586                "UNT" if self.in_message => {
587                    self.buf.push(segment);
588                    self.in_message = false;
589                    let segments = std::mem::take(&mut self.buf);
590                    return Some(Ok(MessageWindow::from_segments(segments)));
591                }
592                _ if self.in_message => self.buf.push(segment),
593                // Envelope segment outside a window — skip.
594                _ => {}
595            }
596        }
597    }
598}
599
600/// Split EDIFACT bytes into one [`MessageWindow`] per `UNH`/`UNT` pair.
601///
602/// Segment text borrows from `input`. Envelope segments (`UNB`, `UNZ`, …) are
603/// skipped automatically.
604///
605/// # Example
606///
607/// ```
608/// use edifact_rs::message_windows;
609///
610/// let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'\
611///               UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'UNT+3+1'\
612///               UNZ+1+1'";
613///
614/// let windows: Vec<_> = message_windows(input).collect::<Result<Vec<_>, _>>()?;
615///
616/// assert_eq!(windows.len(), 1);
617/// assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
618/// # Ok::<(), edifact_rs::EdifactError>(())
619/// ```
620pub fn message_windows(input: &[u8]) -> MessageWindows<'_, crate::FromBytesIter<'_>> {
621    MessageWindows::new(crate::from_bytes(input))
622}
623
624/// Split a reader into one [`OwnedMessageWindow`] per `UNH`/`UNT` pair.
625///
626/// Reads lazily: only enough input to complete one window is consumed per
627/// [`Iterator::next`] call, so peak memory is one message, not one interchange.
628pub fn message_windows_from_reader<R: Read>(
629    reader: R,
630) -> MessageWindows<'static, crate::FromReaderIter<R>> {
631    MessageWindows::new(crate::from_reader(reader))
632}
633
634/// Deserialize one `T` per `UNH`/`UNT` message in `input`.
635///
636/// # Example
637///
638#[cfg_attr(feature = "derive", doc = "```")]
639#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
640/// use edifact_rs::{EdifactDeserialize, deserialize_messages};
641///
642/// #[derive(EdifactDeserialize)]
643/// #[edifact(segment = "BGM")]
644/// struct Bgm {
645///     #[edifact(element = 1)]
646///     number: String,
647/// }
648///
649/// let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
650///               UNH+1+ORDERS:D:96A:UN'BGM+220+PO-1+9'UNT+3+1'\
651///               UNH+2+ORDERS:D:96A:UN'BGM+220+PO-2+9'UNT+3+2'\
652///               UNZ+2+1'";
653///
654/// let orders: Vec<Bgm> = deserialize_messages(input).collect::<Result<_, _>>()?;
655/// assert_eq!(orders[1].number, "PO-2");
656/// # Ok::<(), edifact_rs::EdifactError>(())
657/// ```
658pub fn deserialize_messages<'a, T>(
659    input: &'a [u8],
660) -> impl Iterator<Item = Result<T, EdifactError>> + 'a
661where
662    T: EdifactDeserialize + 'a,
663{
664    message_windows(input).map(|window| T::edifact_deserialize(&window?.segments))
665}
666
667/// Deserialize one `T` per `UNH`/`UNT` message read from `reader`.
668///
669/// The highest-level streaming API: one `T` per message, reading only as much
670/// input as each window needs.
671pub fn deserialize_messages_from_reader<T, R>(
672    reader: R,
673) -> impl Iterator<Item = Result<T, EdifactError>>
674where
675    T: EdifactDeserialize,
676    R: Read,
677{
678    message_windows_from_reader(reader).map(|window| T::edifact_deserialize(&window?.segments))
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684
685    // ── manual test impl ──────────────────────────────────────────────────────
686    #[derive(Debug, PartialEq)]
687    struct BgmSegment {
688        doc_name_code: String,
689        pruef_id: String,
690        msg_function: Option<String>,
691    }
692
693    impl EdifactSegmentTag for BgmSegment {
694        const SEGMENT_TAG: &'static str = "BGM";
695    }
696
697    struct NadM;
698
699    impl EdifactSegmentTag for NadM {
700        const SEGMENT_TAG: &'static str = "NAD";
701        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
702    }
703
704    impl EdifactDeserialize for BgmSegment {
705        fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
706            let seg = find_segment(segments, "BGM").ok_or_else(|| {
707                EdifactError::MissingRequiredElement {
708                    tag: "BGM".to_owned(),
709                    element_index: 0,
710                }
711            })?;
712            Ok(Self {
713                doc_name_code: seg.element_str(0).unwrap_or("").to_owned(),
714                pruef_id: seg.element_str(1).unwrap_or("").to_owned(),
715                msg_function: seg.optional_element(2).map(str::to_owned),
716            })
717        }
718    }
719
720    #[test]
721    fn deserialize_single_segment() {
722        let input = b"BGM+E03+11042+9'";
723        let bgm: BgmSegment = deserialize(input).unwrap();
724        assert_eq!(bgm.doc_name_code, "E03");
725        assert_eq!(bgm.pruef_id, "11042");
726        assert_eq!(bgm.msg_function, Some("9".to_owned()));
727    }
728
729    #[test]
730    fn deserialize_each_is_lazy_over_both_sources() {
731        let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
732
733        let from_slice: Vec<BgmSegment> =
734            deserialize_each(input).collect::<Result<_, _>>().unwrap();
735        let from_reader: Vec<BgmSegment> =
736            deserialize_each_from_reader(std::io::Cursor::new(&input[..]))
737                .collect::<Result<_, _>>()
738                .unwrap();
739
740        assert_eq!(from_slice, from_reader);
741        assert_eq!(from_slice.len(), 2);
742        assert_eq!(from_slice[1].pruef_id, "11043");
743    }
744
745    #[test]
746    fn deserialize_each_stops_at_the_first_match_when_asked() {
747        let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
748        let first: BgmSegment = deserialize_each(input).next().unwrap().unwrap();
749        assert_eq!(first.pruef_id, "11042");
750    }
751
752    #[test]
753    fn qualifier_patterns_match_the_documented_way() {
754        assert!(qualifier_matches_pattern("MS", "M*"));
755        assert!(!qualifier_matches_pattern("MS", "M"));
756        assert!(qualifier_matches_pattern("MS", "MS"));
757        assert!(qualifier_matches_pattern("", ""));
758        // Adversarial patterns are refused rather than backtracked.
759        assert!(!qualifier_matches_pattern("aaaa", "*a*a*a*a*"));
760    }
761
762    #[test]
763    fn typed_qualifier_matching_filters_by_element_zero() {
764        let segments: Vec<Segment<'_>> = crate::from_bytes(b"NAD+MS+1'NAD+BY+2'NAD+MR+3'")
765            .collect::<Result<_, _>>()
766            .unwrap();
767        let matched: Vec<&str> = find_segments_typed::<NadM>(&segments)
768            .map(|s| s.element_str(1).unwrap())
769            .collect();
770        assert_eq!(matched, ["1", "3"]);
771    }
772
773    #[test]
774    fn message_windows_agree_across_both_parsing_paths() {
775        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
776                      UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'UNT+3+1'\
777                      UNH+2+ORDERS:D:96A:UN'BGM+220+B+9'UNT+3+2'\
778                      UNZ+2+1'";
779
780        let sliced: Vec<_> = message_windows(input)
781            .collect::<Result<Vec<_>, _>>()
782            .unwrap();
783        let streamed: Vec<_> = message_windows_from_reader(std::io::Cursor::new(&input[..]))
784            .collect::<Result<Vec<_>, _>>()
785            .unwrap();
786
787        assert_eq!(sliced.len(), 2);
788        assert_eq!(sliced.len(), streamed.len());
789        for (a, b) in sliced.iter().zip(&streamed) {
790            assert_eq!(a.message_type, b.message_type);
791            assert_eq!(a.body().len(), b.body().len());
792        }
793    }
794
795    #[test]
796    fn a_truncated_window_is_an_error_not_a_short_message() {
797        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'";
798        let err = message_windows(input)
799            .collect::<Result<Vec<_>, _>>()
800            .expect_err("an unclosed UNH must not pass as a complete message");
801        assert!(matches!(err, EdifactError::UnexpectedEof { .. }));
802    }
803}