Skip to main content

edifact_rs/
de.rs

1//! Custom deserialization trait for EDIFACT.
2//!
3//! [`EdifactDeserialize`] maps a slice of parsed [`Segment`]s to a Rust value.
4//! [`EdifactSegmentTag`] is a companion trait that carries the segment tag and
5//! optional qualifier at the type level, enabling the blanket
6//! `impl EdifactDeserialize for Vec<T>`.
7
8use crate::{EdifactError, Segment};
9use std::io::Read;
10use std::str::FromStr;
11
12// ── traits ────────────────────────────────────────────────────────────────────
13
14/// Types that can be deserialized from a slice of EDIFACT segments.
15///
16/// Implement manually or derive with `#[derive(EdifactDeserialize)]` from the
17/// `edifact-rs-derive` crate.
18pub trait EdifactDeserialize: Sized {
19    /// Deserialize `Self` from the provided segment slice.
20    ///
21    /// The slice may contain any number of segments; implementations extract
22    /// only the ones they care about and ignore the rest.
23    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError>;
24
25    /// Deserialize `Self` from a slice of owned EDIFACT segments.
26    ///
27    /// The default implementation converts each segment to its borrowed form
28    /// via [`crate::OwnedSegment::as_borrowed`] and calls
29    /// [`edifact_deserialize`][Self::edifact_deserialize].  Types derived with
30    /// `#[derive(EdifactDeserialize)]` override this method to work directly on
31    /// the owned data, avoiding the `Vec<Segment>` allocation entirely.
32    ///
33    /// Manual implementations only need to override this when performance of
34    /// the reader-based streaming path matters.
35    fn edifact_deserialize_owned(segments: &[crate::OwnedSegment]) -> Result<Self, EdifactError> {
36        let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
37        Self::edifact_deserialize(&borrowed)
38    }
39}
40
41/// Types that can be deserialized from a composite EDIFACT element.
42///
43/// Implement this for custom composite structs used with
44/// `#[edifact(composite)]` in derive macros.
45pub trait EdifactCompositeDeserialize: Sized {
46    /// Deserialize `Self` from a composite element.
47    fn edifact_deserialize_composite(composite: CompositeElement<'_>)
48    -> Result<Self, EdifactError>;
49}
50
51impl EdifactCompositeDeserialize for Vec<String> {
52    fn edifact_deserialize_composite(
53        composite: CompositeElement<'_>,
54    ) -> Result<Self, EdifactError> {
55        Ok(composite.iter().map(str::to_owned).collect())
56    }
57}
58
59/// Companion trait that declares a type's segment tag (and optional qualifier).
60///
61/// Required for the `Vec<T>` blanket impl and for finding the right segment in
62/// a message-level struct deserialization.
63pub trait EdifactSegmentTag {
64    /// The 3-character EDIFACT segment tag (e.g. `"BGM"`, `"NAD"`).
65    const SEGMENT_TAG: &'static str;
66
67    /// Optional qualifier pattern to further constrain segment matching.
68    ///
69    /// Examples:
70    /// - `Some("MS")` for exact qualifier matching.
71    /// - `Some("M*")` for wildcard prefix matching (matches `"MS"`, `"MR"`, etc.).
72    const QUALIFIER_PATTERN: Option<&'static str> = None;
73
74    /// Return `true` if `seg`'s qualifier matches this type's qualifier pattern.
75    fn matches_qualifier(seg: &Segment<'_>) -> bool {
76        match Self::QUALIFIER_PATTERN {
77            Some(pattern) => seg
78                .element_str(0)
79                .is_some_and(|q| qualifier_matches_pattern(q, pattern)),
80            None => true,
81        }
82    }
83
84    /// Return `true` if `seg` is the segment this type maps to.
85    ///
86    /// Default: `seg.tag == Self::SEGMENT_TAG`.  Override to also match on a
87    /// qualifier (e.g. `NAD+BY` — element 0 = `"BY"`).
88    fn matches_segment(seg: &Segment<'_>) -> bool {
89        seg.tag == Self::SEGMENT_TAG && Self::matches_qualifier(seg)
90    }
91
92    /// Like [`matches_segment`][Self::matches_segment] but works directly on an
93    /// [`crate::OwnedSegment`] without incurring the `Vec` allocation of
94    /// [`crate::OwnedSegment::as_borrowed`].
95    fn matches_owned_segment(seg: &crate::OwnedSegment) -> bool {
96        if seg.tag != Self::SEGMENT_TAG {
97            return false;
98        }
99        match Self::QUALIFIER_PATTERN {
100            None => true,
101            Some(pattern) => {
102                let q = seg
103                    .elements
104                    .first()
105                    .and_then(|e| e.components.first())
106                    .map(|c| c.as_str())
107                    .unwrap_or("");
108                qualifier_matches_pattern(q, pattern)
109            }
110        }
111    }
112}
113
114// ── blanket impl for Vec<T> ───────────────────────────────────────────────────
115
116/// Deserializes each segment matching `T::matches_segment` as an independent
117/// single-segment slice, collecting the results.
118impl<T> EdifactDeserialize for Vec<T>
119where
120    T: EdifactDeserialize + EdifactSegmentTag,
121{
122    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
123        segments
124            .iter()
125            .filter(|s| T::matches_segment(s))
126            .map(|seg| T::edifact_deserialize(std::slice::from_ref(seg)))
127            .collect()
128    }
129
130    fn edifact_deserialize_owned(segments: &[crate::OwnedSegment]) -> Result<Self, EdifactError> {
131        segments
132            .iter()
133            .filter(|s| T::matches_owned_segment(s))
134            .map(|seg| T::edifact_deserialize_owned(std::slice::from_ref(seg)))
135            .collect()
136    }
137}
138
139// ── public API ────────────────────────────────────────────────────────────────
140
141/// Deserialize a value of type `T` from EDIFACT bytes.
142///
143/// Unlike [`crate::from_bytes`], which parses bytes into raw [`Segment`]s, this
144/// function fully deserializes the payload into a typed Rust value via [`EdifactDeserialize`].
145///
146/// This API currently buffers all parsed segments into a `Vec` before invoking
147/// typed deserialization.
148pub fn deserialize<T: EdifactDeserialize>(input: &[u8]) -> Result<T, EdifactError> {
149    let segments: Vec<Segment<'_>> = crate::from_bytes(input).collect::<Result<_, _>>()?;
150    T::edifact_deserialize(&segments)
151}
152
153/// Stream-parse EDIFACT bytes and deserialize the first matching segment as `T`.
154///
155/// This avoids allocating a full `Vec<Segment>` and is intended for low-memory
156/// extraction of segment-scoped types.
157pub fn deserialize_first_streaming<T>(input: &[u8]) -> Result<T, EdifactError>
158where
159    T: EdifactDeserialize + EdifactSegmentTag,
160{
161    for segment in crate::from_bytes(input) {
162        let segment = segment?;
163        if T::matches_segment(&segment) {
164            return T::edifact_deserialize(std::slice::from_ref(&segment));
165        }
166    }
167
168    Err(EdifactError::MissingSegment {
169        tag: T::SEGMENT_TAG.to_owned(),
170        expected_position: "any position in input".to_owned(),
171    })
172}
173
174/// Stream-parse EDIFACT bytes and deserialize all matching segments as `Vec<T>`.
175///
176/// This avoids buffering non-matching segments in memory.
177pub fn deserialize_all_streaming<T>(input: &[u8]) -> Result<Vec<T>, EdifactError>
178where
179    T: EdifactDeserialize + EdifactSegmentTag,
180{
181    let mut out = Vec::new();
182    for segment in crate::from_bytes(input) {
183        let segment = segment?;
184        if T::matches_segment(&segment) {
185            out.push(T::edifact_deserialize(std::slice::from_ref(&segment))?);
186        }
187    }
188    Ok(out)
189}
190
191/// Stream-parse EDIFACT from a reader and deserialize the first matching segment as `T`.
192///
193/// This is the low-memory typed path for large payloads read from I/O streams.
194pub fn deserialize_first_from_reader<T, R>(reader: R) -> Result<T, EdifactError>
195where
196    T: EdifactDeserialize + EdifactSegmentTag,
197    R: Read,
198{
199    for segment in crate::from_reader_iter(reader) {
200        let segment = segment?;
201        // O(1) tag + qualifier check before paying for as_borrowed().
202        if !T::matches_owned_segment(&segment) {
203            continue;
204        }
205        let borrowed = segment.as_borrowed();
206        return T::edifact_deserialize(std::slice::from_ref(&borrowed));
207    }
208
209    Err(EdifactError::MissingSegment {
210        tag: T::SEGMENT_TAG.to_owned(),
211        expected_position: "any position in input".to_owned(),
212    })
213}
214
215/// Stream-parse EDIFACT from a reader and deserialize all matching segments as `Vec<T>`.
216pub fn deserialize_all_from_reader<T, R>(reader: R) -> Result<Vec<T>, EdifactError>
217where
218    T: EdifactDeserialize + EdifactSegmentTag,
219    R: Read,
220{
221    let mut out = Vec::new();
222    for segment in crate::from_reader_iter(reader) {
223        let segment = segment?;
224        // O(1) tag + qualifier check before paying for as_borrowed().
225        if !T::matches_owned_segment(&segment) {
226            continue;
227        }
228        let borrowed = segment.as_borrowed();
229        out.push(T::edifact_deserialize(std::slice::from_ref(&borrowed))?);
230    }
231    Ok(out)
232}
233
234/// Deserialize a value of type `T` from an EDIFACT string.
235pub fn deserialize_str<T: EdifactDeserialize>(input: &str) -> Result<T, EdifactError> {
236    deserialize(input.as_bytes())
237}
238
239// ── helper functions ──────────────────────────────────────────────────────────
240
241/// Find the first segment with the given tag.
242pub fn find_segment<'s, 'd>(segments: &'s [Segment<'d>], tag: &str) -> Option<&'s Segment<'d>> {
243    segments.iter().find(|s| s.tag == tag)
244}
245
246/// Iterate over all segments with the given tag without allocating a `Vec`.
247pub fn find_segments_iter<'s, 'd: 's>(
248    segments: &'s [Segment<'d>],
249    tag: &'s str,
250) -> impl Iterator<Item = &'s Segment<'d>> {
251    segments.iter().filter(move |s| s.tag == tag)
252}
253
254/// Find the first segment matching `tag` whose element 0 equals `qualifier`.
255pub fn find_qualified_segment<'s, 'd>(
256    segments: &'s [Segment<'d>],
257    tag: &str,
258    qualifier: &str,
259) -> Option<&'s Segment<'d>> {
260    segments
261        .iter()
262        .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
263}
264
265/// Find the first segment by type-level qualifier pattern.
266pub fn find_segment_typed<'s, 'd, T>(segments: &'s [Segment<'d>]) -> Option<&'s Segment<'d>>
267where
268    T: EdifactSegmentTag,
269{
270    segments.iter().find(|s| T::matches_segment(s))
271}
272
273/// Collect all segments by type-level qualifier pattern.
274pub fn find_segments_typed<'s, 'd, T>(
275    segments: &'s [Segment<'d>],
276) -> Vec<&'s Segment<'d>>
277where
278    T: EdifactSegmentTag,
279{
280    segments.iter().filter(|s| T::matches_segment(s)).collect()
281}
282
283/// Collect contiguous groups of segments that match `T`.
284pub fn contiguous_groups_by_qualifier<'s, 'd, T>(
285    segments: &'s [Segment<'d>],
286) -> Vec<&'s [Segment<'d>]>
287where
288    T: EdifactSegmentTag,
289{
290    let mut groups = Vec::new();
291    let mut idx = 0;
292
293    while idx < segments.len() {
294        if T::matches_segment(&segments[idx]) {
295            let start = idx;
296            idx += 1;
297            while idx < segments.len() && T::matches_segment(&segments[idx]) {
298                idx += 1;
299            }
300            groups.push(&segments[start..idx]);
301        } else {
302            idx += 1;
303        }
304    }
305
306    groups
307}
308
309/// Return `true` if all segments matching `T` are in one contiguous block.
310pub fn groups_are_contiguous_by_qualifier<T>(segments: &[Segment<'_>]) -> bool
311where
312    T: EdifactSegmentTag,
313{
314    let mut seen_match = false;
315    let mut seen_gap_after_match = false;
316
317    for seg in segments {
318        if T::matches_segment(seg) {
319            if seen_gap_after_match {
320                return false;
321            }
322            seen_match = true;
323        } else if seen_match {
324            seen_gap_after_match = true;
325        }
326    }
327
328    true
329}
330
331/// Match a qualifier value against an exact or wildcard pattern.
332///
333/// Rules:
334/// - If `pattern` contains `*`, it is treated as a glob wildcard (e.g. `"M*"` matches `"MS"`, `"MR"`).
335/// - If no wildcard is present, exact match is required.
336///
337/// Prefix matching without an explicit `*` was deliberately removed: `"M"` matches only `"M"`,
338/// not `"MS"` or `"MR"`.  Use `"M*"` for prefix semantics.
339pub fn qualifier_matches_pattern(value: &str, pattern: &str) -> bool {
340    if pattern.is_empty() {
341        return value.is_empty();
342    }
343
344    if !pattern.contains('*') {
345        return value == pattern;
346    }
347
348    // Fast path: single wildcard (dominant case — e.g. "M*" or "*:MS")
349    if let Some((prefix, suffix)) = pattern.split_once('*') {
350        // Only one wildcard — prefix and suffix cannot overlap in a second split.
351        if !pattern[prefix.len() + 1..].contains('*') {
352            return value.len() >= prefix.len() + suffix.len()
353                && value.starts_with(prefix)
354                && value.ends_with(suffix)
355                && {
356                    // Ensure prefix and suffix don't overlap.
357                    let mid_start = prefix.len();
358                    let mid_end = value.len().saturating_sub(suffix.len());
359                    mid_start <= mid_end
360                };
361        }
362    }
363
364    // General multi-wildcard path.
365    let parts: smallvec::SmallVec<[&str; 4]> = pattern.split('*').collect();
366    let prefix = parts[0];
367    let suffix = parts[parts.len() - 1];
368
369    if !value.starts_with(prefix) || !value.ends_with(suffix) {
370        return false;
371    }
372
373    let mid_start = prefix.len();
374    let mid_end = value.len().saturating_sub(suffix.len());
375
376    if mid_start > mid_end {
377        return parts[1..parts.len() - 1].iter().all(|p| p.is_empty());
378    }
379
380    let mut remaining = &value[mid_start..mid_end];
381
382    for part in &parts[1..parts.len() - 1] {
383        if part.is_empty() {
384            continue;
385        }
386        match remaining.find(part) {
387            Some(idx) => remaining = &remaining[idx + part.len()..],
388            None => return false,
389        }
390    }
391
392    true
393}
394
395/// Extract the string value of element `idx` from `seg`, or `""` if absent.
396#[inline]
397pub fn element_str<'s>(seg: &'s Segment<'_>, idx: usize) -> &'s str {
398    seg.element_str(idx).unwrap_or("")
399}
400
401// ── segment accessor helpers ───────────────────────────────────────────────────
402
403/// Extract a required text element from a segment.
404///
405/// Returns the element's first component, or an error if absent or empty.
406pub fn required_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Result<&'a str, EdifactError> {
407    seg.element_str(idx)
408        .filter(|s| !s.is_empty())
409        .ok_or_else(|| EdifactError::MissingRequiredElement {
410            tag: seg.tag.to_owned(),
411            element_index: idx,
412        })
413}
414
415/// Extract an optional text element from a segment.
416///
417/// Returns the element's first component, or None if absent or empty.
418pub fn optional_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Option<&'a str> {
419    seg.element_str(idx)
420        .filter(|s| !s.is_empty())
421}
422
423/// Extract a required component from a segment element.
424///
425/// Returns the component value, or an error if the element or component is absent.
426pub fn required_component<'a>(
427    seg: &'a Segment<'_>,
428    elem_idx: usize,
429    comp_idx: usize,
430) -> Result<&'a str, EdifactError> {
431    seg.elements
432        .get(elem_idx)
433        .and_then(|elem| elem.get_component(comp_idx))
434        .filter(|s| !s.is_empty())
435        .ok_or_else(|| EdifactError::MissingRequiredElement {
436            tag: seg.tag.to_owned(),
437            element_index: elem_idx,
438        })
439}
440
441/// Extract an optional component from a segment element.
442///
443/// Returns the component value, or None if absent or empty.
444pub fn optional_component<'a>(seg: &'a Segment<'_>, elem_idx: usize, comp_idx: usize) -> Option<&'a str> {
445    seg.elements
446        .get(elem_idx)
447        .and_then(|elem| elem.get_component(comp_idx))
448        .filter(|s| !s.is_empty())
449}
450
451/// Iterate over all components of an element without allocating a `Vec`.
452///
453/// Yields an empty iterator if the element is absent.
454pub fn get_components_iter<'a>(
455    seg: &'a Segment<'_>,
456    idx: usize,
457) -> impl Iterator<Item = &'a str> {
458    seg.elements
459        .get(idx)
460        .into_iter()
461        .flat_map(|elem| elem.components.iter().map(|c| c.as_ref()))
462}
463
464/// A composite data element wrapper for clearer ergonomics.
465pub struct CompositeElement<'a> {
466    components: &'a [std::borrow::Cow<'a, str>],
467}
468
469impl<'a> CompositeElement<'a> {
470    /// Get the component at index `i`, or None if absent.
471    pub fn get(&self, i: usize) -> Option<&'a str> {
472        self.components.get(i).map(|c| c.as_ref())
473    }
474
475    /// Get the component at index `i`, or empty string if absent.
476    pub fn get_or_empty(&self, i: usize) -> &'a str {
477        self.get(i).unwrap_or("")
478    }
479
480    /// Get the number of components.
481    pub fn len(&self) -> usize {
482        self.components.len()
483    }
484
485    /// Check if the composite is empty.
486    pub fn is_empty(&self) -> bool {
487        self.components.is_empty()
488    }
489
490    /// Iterate over all components.
491    pub fn iter(&self) -> impl Iterator<Item = &'a str> {
492        self.components.iter().map(|c| c.as_ref())
493    }
494
495    /// Create a `CompositeElement` from a pre-existing component slice.
496    ///
497    /// Used internally by [`edifact_deserialize_owned`][EdifactDeserialize::edifact_deserialize_owned]
498    /// generated code to pass component data without converting the whole segment.
499    pub fn from_slice(components: &'a [std::borrow::Cow<'a, str>]) -> Self {
500        Self { components }
501    }
502}
503
504/// Get a composite element from a segment with clearer ergonomics.
505pub fn composite_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Option<CompositeElement<'a>> {
506    seg.elements.get(idx).map(|elem| CompositeElement {
507        components: &elem.components,
508    })
509}
510
511/// Find the first [`OwnedSegment`] with the given tag.
512///
513/// Zero-allocation counterpart of [`find_segment`] for use in
514/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
515///
516/// [`OwnedSegment`]: crate::OwnedSegment
517pub fn find_segment_owned<'s>(
518    segments: &'s [crate::OwnedSegment],
519    tag: &str,
520) -> Option<&'s crate::OwnedSegment> {
521    segments.iter().find(|s| s.tag == tag)
522}
523
524/// Find the first [`OwnedSegment`] with the given tag **and** qualifier.
525///
526/// The qualifier is compared against the first component of element 0.
527/// Zero-allocation counterpart of [`find_qualified_segment`] for use in
528/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
529///
530/// [`OwnedSegment`]: crate::OwnedSegment
531pub fn find_qualified_segment_owned<'s>(
532    segments: &'s [crate::OwnedSegment],
533    tag: &str,
534    qualifier: &str,
535) -> Option<&'s crate::OwnedSegment> {
536    segments.iter().find(|s| {
537        s.tag == tag && s.element_str(0).unwrap_or("") == qualifier
538    })
539}
540
541/// Segment accessor trait for ergonomic typed extraction.
542pub trait SegmentAccessor<'a> {
543    /// Get non-empty element text at index `idx`.
544    fn get_element(&'a self, idx: usize) -> Option<&'a str>;
545    /// Get non-empty component text at element/component indexes.
546    fn get_component(&'a self, elem: usize, comp: usize) -> Option<&'a str>;
547    /// Get a composite wrapper for element `idx`.
548    fn get_composite(&'a self, idx: usize) -> Option<CompositeElement<'a>>;
549
550    /// Get required non-empty element text.
551    fn text_element(&'a self, idx: usize) -> Result<&'a str, EdifactError>;
552    /// Get optional non-empty element text.
553    fn optional_element(&'a self, idx: usize) -> Option<&'a str>;
554    /// Parse a typed code value from a required element.
555    fn code_element<T: FromStr>(&'a self, idx: usize) -> Result<T, EdifactError>;
556    /// Get required non-empty composite component.
557    fn required_composite(&'a self, elem: usize, comp: usize) -> Result<&'a str, EdifactError>;
558    /// Get `count` required components starting at `start_idx` from element `elem`.
559    fn repeating_components(
560        &'a self,
561        elem: usize,
562        start_idx: usize,
563        count: usize,
564    ) -> Result<Vec<&'a str>, EdifactError>;
565
566    /// Iterate over `count` required components starting at `start_idx` from element `elem`.
567    ///
568    /// Allocation-free alternative to [`repeating_components`][Self::repeating_components];
569    /// the caller supplies the iteration budget and consumes results on the fly.
570    fn repeating_components_iter(
571        &'a self,
572        elem: usize,
573        start_idx: usize,
574        count: usize,
575    ) -> impl Iterator<Item = Result<&'a str, EdifactError>> + 'a;
576}
577
578impl<'s, 'd> SegmentAccessor<'s> for Segment<'d>
579where
580    'd: 's,
581{
582    fn get_element(&'s self, idx: usize) -> Option<&'s str> {
583        self.element_str(idx).filter(|s| !s.is_empty())
584    }
585
586    fn get_component(&'s self, elem: usize, comp: usize) -> Option<&'s str> {
587        self.elements
588            .get(elem)
589            .and_then(|e| e.get_component(comp))
590            .filter(|s| !s.is_empty())
591    }
592
593    fn get_composite(&'s self, idx: usize) -> Option<CompositeElement<'s>> {
594        composite_element(self, idx)
595    }
596
597    fn text_element(&'s self, idx: usize) -> Result<&'s str, EdifactError> {
598        <Self as SegmentAccessor>::get_element(self, idx).ok_or_else(|| {
599            EdifactError::MissingRequiredElement {
600                tag: self.tag.to_owned(),
601                element_index: idx,
602            }
603        })
604    }
605
606    fn optional_element(&'s self, idx: usize) -> Option<&'s str> {
607        <Self as SegmentAccessor>::get_element(self, idx)
608    }
609
610    fn code_element<T: FromStr>(&'s self, idx: usize) -> Result<T, EdifactError> {
611        let raw = self.text_element(idx)?;
612        raw.parse::<T>().map_err(|_| EdifactError::InvalidText {
613            offset: self.element_span(idx).map(|s| s.start).unwrap_or(self.span.start),
614        })
615    }
616
617    fn required_composite(&'s self, elem: usize, comp: usize) -> Result<&'s str, EdifactError> {
618        <Self as SegmentAccessor>::get_component(self, elem, comp).ok_or_else(|| {
619            EdifactError::MissingRequiredElement {
620                tag: self.tag.to_owned(),
621                element_index: elem,
622            }
623        })
624    }
625
626    fn repeating_components(
627        &'s self,
628        elem: usize,
629        start_idx: usize,
630        count: usize,
631    ) -> Result<Vec<&'s str>, EdifactError> {
632        let comp =
633            self.get_composite(elem)
634                .ok_or_else(|| EdifactError::MissingRequiredElement {
635                    tag: self.tag.to_owned(),
636                    element_index: elem,
637                })?;
638
639        (start_idx..start_idx + count)
640            .map(|idx| {
641                comp.get(idx).filter(|s| !s.is_empty()).ok_or_else(|| {
642                    EdifactError::MissingRequiredElement {
643                        tag: self.tag.to_owned(),
644                        element_index: elem,
645                    }
646                })
647            })
648            .collect()
649    }
650
651    fn repeating_components_iter(
652        &'s self,
653        elem: usize,
654        start_idx: usize,
655        count: usize,
656    ) -> impl Iterator<Item = Result<&'s str, EdifactError>> + 's {
657        let tag = self.tag;
658        let components = self
659            .elements
660            .get(elem)
661            .map(|e| e.components.as_slice())
662            .unwrap_or(&[]);
663        (start_idx..start_idx + count).map(move |idx| {
664            components
665                .get(idx)
666                .map(|c| c.as_ref())
667                .filter(|s| !s.is_empty())
668                .ok_or_else(|| EdifactError::MissingRequiredElement {
669                    tag: tag.to_owned(),
670                    element_index: elem,
671                })
672        })
673    }
674}
675
676// ── message-window streaming ──────────────────────────────────────────────────
677
678/// An iterator that groups borrowed EDIFACT segments into per-message windows.
679///
680/// Zero-copy counterpart to [`MessageWindowsIter`] for in-memory byte slices.
681/// Each yielded `Vec<Segment<'_>>` borrows from the original input; no heap
682/// allocations occur per segment.  Envelope segments outside a `UNH..UNT` pair
683/// are silently skipped.
684///
685/// Obtain this via [`message_windows_bytes`].
686pub struct MessageWindowsSliceIter<'a> {
687    inner: crate::FromBytesIter<'a>,
688    buf: Vec<crate::Segment<'a>>,
689    in_message: bool,
690    done: bool,
691}
692
693impl<'a> MessageWindowsSliceIter<'a> {
694    fn new(inner: crate::FromBytesIter<'a>) -> Self {
695        Self {
696            inner,
697            buf: Vec::new(),
698            in_message: false,
699            done: false,
700        }
701    }
702}
703
704impl<'a> Iterator for MessageWindowsSliceIter<'a> {
705    type Item = Result<Vec<crate::Segment<'a>>, EdifactError>;
706
707    fn next(&mut self) -> Option<Self::Item> {
708        if self.done {
709            return None;
710        }
711        loop {
712            let segment = match self.inner.next() {
713                Some(Ok(s)) => s,
714                Some(Err(e)) => {
715                    self.done = true;
716                    return Some(Err(e));
717                }
718                None => {
719                    self.done = true;
720                    if self.in_message && !self.buf.is_empty() {
721                        self.in_message = false;
722                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
723                        return Some(Err(EdifactError::UnexpectedEof { offset }));
724                    }
725                    return None;
726                }
727            };
728
729            match segment.tag {
730                "UNH" => {
731                    if self.in_message {
732                        self.buf.clear();
733                        self.in_message = false;
734                        self.done = true;
735                        return Some(Err(EdifactError::ValidationFailed {
736                            error_count: 1,
737                            first_message:
738                                "UNH seen while a message window is already open (missing UNT)"
739                                    .to_owned(),
740                        }));
741                    }
742                    self.buf.clear();
743                    self.in_message = true;
744                    self.buf.push(segment);
745                }
746                "UNT" if self.in_message => {
747                    self.buf.push(segment);
748                    self.in_message = false;
749                    return Some(Ok(std::mem::take(&mut self.buf)));
750                }
751                _ if self.in_message => {
752                    self.buf.push(segment);
753                }
754                _ => {
755                    // Envelope segment outside a window — skip.
756                }
757            }
758        }
759    }
760}
761
762/// An iterator that groups owned EDIFACT segments into per-message windows.
763///
764/// Each yielded item is a `Vec<OwnedSegment>` containing the segments for one
765/// complete `UNH..UNT` message, inclusive of both service segments.
766/// Envelope-level segments (`UNB`, `UNG`, `UNZ`, `UNE`) that sit outside any
767/// `UNH..UNT` pair are silently skipped.
768///
769/// # Errors
770///
771/// - An inner-iterator error is forwarded immediately and iteration stops.
772/// - A `UNH` seen while a prior window is still open (missing `UNT`) is an error.
773/// - Input that ends while a `UNH` window is open (stream truncation) yields
774///   `Err(EdifactError::UnexpectedEof { … })` before returning `None`.
775///
776/// # Construction
777///
778/// Use [`message_windows_from_reader`] or [`message_windows_bytes`] to
779/// obtain a `MessageWindowsIter` directly.  For fully custom sources, call
780/// [`MessageWindowsIter::new`] with any `Iterator<Item = Result<OwnedSegment,
781/// EdifactError>>`.
782pub struct MessageWindowsIter<I> {
783    inner: I,
784    buf: Vec<crate::OwnedSegment>,
785    in_message: bool,
786    /// Set to `true` after any terminal condition (error or clean EOF) so that
787    /// subsequent `next()` calls immediately return `None`.
788    done: bool,
789}
790
791impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> MessageWindowsIter<I> {
792    /// Wrap any owned-segment iterator as a message-window iterator.
793    pub fn new(inner: I) -> Self {
794        Self {
795            inner,
796            buf: Vec::new(),
797            in_message: false,
798            done: false,
799        }
800    }
801}
802
803impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> Iterator
804    for MessageWindowsIter<I>
805{
806    type Item = Result<Vec<crate::OwnedSegment>, EdifactError>;
807
808    fn next(&mut self) -> Option<Self::Item> {
809        if self.done {
810            return None;
811        }
812        loop {
813            let segment = match self.inner.next() {
814                Some(Ok(s)) => s,
815                Some(Err(e)) => {
816                    self.done = true;
817                    return Some(Err(e));
818                }
819                None => {
820                    self.done = true;
821                    // A window that opened (UNH seen) but never closed (no UNT)
822                    // means the stream was truncated — surface as an error.
823                    if self.in_message && !self.buf.is_empty() {
824                        self.in_message = false;
825                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
826                        return Some(Err(EdifactError::UnexpectedEof { offset }));
827                    }
828                    return None;
829                }
830            };
831
832            match segment.tag.as_str() {
833                "UNH" => {
834                    if self.in_message {
835                        // Malformed: new UNH without a prior UNT.
836                        self.buf.clear();
837                        self.in_message = false;
838                        self.done = true;
839                        return Some(Err(EdifactError::ValidationFailed {
840                            error_count: 1,
841                            first_message:
842                                "UNH seen while a message window is already open (missing UNT)"
843                                    .to_owned(),
844                        }));
845                    }
846                    self.buf.clear();
847                    self.in_message = true;
848                    self.buf.push(segment);
849                }
850                "UNT" if self.in_message => {
851                    self.buf.push(segment);
852                    self.in_message = false;
853                    return Some(Ok(std::mem::take(&mut self.buf)));
854                }
855                _ if self.in_message => {
856                    self.buf.push(segment);
857                }
858                _ => {
859                    // Envelope segment outside a window — skip.
860                }
861            }
862        }
863    }
864}
865
866/// Stream-parse EDIFACT bytes into an iterator of per-message windows.
867///
868/// Each window is a `Vec<Segment<'_>>` spanning one `UNH..UNT` pair, with
869/// segments borrowing from `input` — **zero heap allocations per segment**.
870/// Envelope segments (`UNB`, `UNZ`, …) are skipped automatically.
871///
872/// # Example
873/// ```
874/// use edifact_rs::message_windows_bytes;
875/// let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'\
876///               UNH+1+ORDERS:D:96A:UN'\
877///               BGM+220+PO-001+9'\
878///               UNT+3+1'\
879///               UNZ+1+1'";
880///
881/// let windows: Vec<_> = message_windows_bytes(input)
882///     .collect::<Result<_, _>>()
883///     .unwrap();
884/// assert_eq!(windows.len(), 1);
885/// assert_eq!(windows[0][0].tag, "UNH");
886/// assert_eq!(windows[0].last().unwrap().tag, "UNT");
887/// ```
888pub fn message_windows_bytes(input: &[u8]) -> MessageWindowsSliceIter<'_> {
889    MessageWindowsSliceIter::new(crate::from_bytes(input))
890}
891
892/// Stream-parse EDIFACT from a reader into an iterator of per-message windows.
893///
894/// Each window is a `Vec<OwnedSegment>` spanning one `UNH..UNT` pair.
895/// This variant reads lazily — only enough input to complete one window is
896/// consumed per [`Iterator::next`] call.
897pub fn message_windows_from_reader<R: Read>(
898    reader: R,
899) -> MessageWindowsIter<crate::FromReaderIter<R>> {
900    MessageWindowsIter::new(crate::from_reader_iter(reader))
901}
902
903/// Stream typed messages from a reader by deserializing each `UNH..UNT` window.
904///
905/// This is the highest-level streaming API: it returns one `T` per message,
906/// reading only as much data as needed to complete each window.
907///
908/// Each message window is deserialized via
909/// [`EdifactDeserialize::edifact_deserialize_owned`], which avoids the
910/// intermediate `Vec<Segment<'_>>` allocation incurred by the slice-based path.
911/// Types derived with `#[derive(EdifactDeserialize)]` provide an efficient
912/// override; manual implementations fall back to [`crate::OwnedSegment::as_borrowed`].
913///
914/// # Example
915/// ```ignore
916/// // Assuming `OrdersMessage` implements `EdifactDeserialize`:
917/// let messages: Vec<OrdersMessage> =
918///     deserialize_messages_from_reader::<OrdersMessage, _>(reader)
919///         .collect::<Result<_, _>>()?;
920/// ```
921pub fn deserialize_messages_from_reader<T, R>(
922    reader: R,
923) -> impl Iterator<Item = Result<T, EdifactError>>
924where
925    T: EdifactDeserialize,
926    R: Read,
927{
928    message_windows_from_reader(reader).map(|window| {
929        let window = window?;
930        T::edifact_deserialize_owned(&window)
931    })
932}
933
934/// Stream typed messages from a byte slice by deserializing each `UNH..UNT` window.
935pub fn deserialize_messages_bytes<T>(
936    input: &[u8],
937) -> impl Iterator<Item = Result<T, EdifactError>> + '_
938where
939    T: EdifactDeserialize,
940{
941    message_windows_bytes(input).map(|window| {
942        let window = window?;
943        T::edifact_deserialize(&window)
944    })
945}
946
947#[cfg(test)]
948mod tests {
949    use super::*;
950
951    // ── manual test impl ──────────────────────────────────────────────────────
952    #[derive(Debug, PartialEq)]
953    struct BgmSegment {
954        doc_name_code: String,
955        pruef_id: String,
956        msg_function: Option<String>,
957    }
958
959    impl EdifactSegmentTag for BgmSegment {
960        const SEGMENT_TAG: &'static str = "BGM";
961    }
962
963    struct NadM;
964
965    impl EdifactSegmentTag for NadM {
966        const SEGMENT_TAG: &'static str = "NAD";
967        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
968    }
969
970    struct NadWildcard;
971
972    impl EdifactSegmentTag for NadWildcard {
973        const SEGMENT_TAG: &'static str = "NAD";
974        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
975    }
976
977    impl EdifactDeserialize for BgmSegment {
978        fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
979            let seg = find_segment(segments, "BGM").ok_or_else(|| {
980                EdifactError::MissingRequiredElement {
981                    tag: "BGM".to_owned(),
982                    element_index: 0,
983                }
984            })?;
985            Ok(Self {
986                doc_name_code: element_str(seg, 0).to_owned(),
987                pruef_id: element_str(seg, 1).to_owned(),
988                msg_function: seg
989                    .element_str(2)
990                    .filter(|s| !s.is_empty())
991                    .map(str::to_owned),
992            })
993        }
994    }
995
996    #[test]
997    fn deserialize_single_segment() {
998        let input = b"BGM+E03+11042+9'";
999        let bgm: BgmSegment = deserialize(input).unwrap();
1000        assert_eq!(bgm.doc_name_code, "E03");
1001        assert_eq!(bgm.pruef_id, "11042");
1002        assert_eq!(bgm.msg_function, Some("9".to_owned()));
1003    }
1004
1005    #[test]
1006    fn streaming_deserialize_first_from_bytes() {
1007        let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
1008        let bgm: BgmSegment = deserialize_first_streaming(input).unwrap();
1009        assert_eq!(bgm.pruef_id, "11042");
1010    }
1011
1012    #[test]
1013    fn streaming_deserialize_all_from_bytes() {
1014        let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
1015        let bgms: Vec<BgmSegment> = deserialize_all_streaming(input).unwrap();
1016        assert_eq!(bgms.len(), 2);
1017        assert_eq!(bgms[0].pruef_id, "11042");
1018        assert_eq!(bgms[1].pruef_id, "11043");
1019    }
1020
1021    #[test]
1022    fn streaming_deserialize_first_from_reader() {
1023        let input = std::io::Cursor::new(b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'".to_vec());
1024        let bgm: BgmSegment = deserialize_first_from_reader(input).unwrap();
1025        assert_eq!(bgm.pruef_id, "11042");
1026    }
1027
1028    #[test]
1029    fn streaming_deserialize_all_from_reader() {
1030        let input = std::io::Cursor::new(b"BGM+E03+11042+9'BGM+E01+11043+9'".to_vec());
1031        let bgms: Vec<BgmSegment> = deserialize_all_from_reader(input).unwrap();
1032        assert_eq!(bgms.len(), 2);
1033        assert_eq!(bgms[0].pruef_id, "11042");
1034        assert_eq!(bgms[1].pruef_id, "11043");
1035    }
1036
1037    #[test]
1038    fn missing_segment_returns_error() {
1039        let input = b"DTM+137:20230401:102'";
1040        let result: Result<BgmSegment, _> = deserialize(input);
1041        assert!(result.is_err());
1042    }
1043
1044    #[test]
1045    fn vec_collects_all_matching_segments() {
1046        let input = b"DTM+137:20230401:102'BGM+E03+11042+9'BGM+E01+11043+9'";
1047        let bgms: Vec<BgmSegment> = deserialize(input).unwrap();
1048        assert_eq!(bgms.len(), 2);
1049        assert_eq!(bgms[0].pruef_id, "11042");
1050        assert_eq!(bgms[1].pruef_id, "11043");
1051    }
1052
1053    #[test]
1054    fn find_qualified_segment_matches_qualifier() {
1055        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1056        let segments: Vec<Segment<'_>> =
1057            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1058        let nad_ms = find_qualified_segment(&segments, "NAD", "MS");
1059        let nad_mr = find_qualified_segment(&segments, "NAD", "MR");
1060        assert!(nad_ms.is_some());
1061        assert!(nad_mr.is_some());
1062        assert_eq!(element_str(nad_ms.unwrap(), 0), "MS");
1063        assert_eq!(element_str(nad_mr.unwrap(), 0), "MR");
1064    }
1065
1066    #[test]
1067    fn round_trip_str_api() {
1068        let input = "BGM+E03+11042+9'";
1069        let bgm: BgmSegment = deserialize_str(input).unwrap();
1070        assert_eq!(bgm.pruef_id, "11042");
1071    }
1072
1073    #[test]
1074    fn required_element_extraction() {
1075        let input = b"BGM+E03+11042+9'";
1076        let segments: Vec<Segment<'_>> =
1077            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1078        let seg = &segments[0];
1079
1080        assert_eq!(required_element(seg, 0).unwrap(), "E03");
1081        assert_eq!(required_element(seg, 1).unwrap(), "11042");
1082        // Element 5 doesn't exist
1083        assert!(required_element(seg, 5).is_err());
1084    }
1085
1086    #[test]
1087    fn optional_element_extraction() {
1088        let input = b"BGM+E03+11042+9'BGM+E01++absent'";
1089        let segments: Vec<Segment<'_>> =
1090            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1091
1092        // First segment
1093        assert_eq!(optional_element(&segments[0], 0), Some("E03"));
1094        assert_eq!(optional_element(&segments[0], 1), Some("11042"));
1095        assert_eq!(optional_element(&segments[0], 5), None);
1096
1097        // Second segment with empty element
1098        assert_eq!(optional_element(&segments[1], 1), None);
1099    }
1100
1101    #[test]
1102    fn component_extraction() {
1103        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1104        let segments: Vec<Segment<'_>> =
1105            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1106        let seg = &segments[0];
1107
1108        assert_eq!(required_component(seg, 0, 0).unwrap(), "UNOA");
1109        assert_eq!(required_component(seg, 0, 1).unwrap(), "1");
1110        // Non-existent component
1111        assert!(required_component(seg, 0, 5).is_err());
1112    }
1113
1114    #[test]
1115    fn composite_element_helper() {
1116        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1117        let segments: Vec<Segment<'_>> =
1118            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1119        let seg = &segments[0];
1120
1121        let comp = composite_element(seg, 0).unwrap();
1122        assert_eq!(comp.len(), 2);
1123        assert_eq!(comp.get(0), Some("UNOA"));
1124        assert_eq!(comp.get(1), Some("1"));
1125        assert_eq!(comp.get(5), None);
1126        assert_eq!(comp.get_or_empty(5), "");
1127    }
1128
1129    #[test]
1130    fn get_all_components() {
1131        // UNB has composite element: UNOA:1
1132        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1133        let segments: Vec<Segment<'_>> =
1134            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1135        let seg = &segments[0];
1136
1137        let comps: Vec<&str> = get_components_iter(seg, 0).collect(); // First element is UNOA:1
1138        assert!(!comps.is_empty(), "Expected components but got empty");
1139        assert_eq!(comps.len(), 2);
1140        assert_eq!(comps[0], "UNOA");
1141        assert_eq!(comps[1], "1");
1142    }
1143
1144    #[test]
1145    fn qualifier_pattern_matching_supports_exact_and_wildcard() {
1146        // Exact match (no wildcard)
1147        assert!(qualifier_matches_pattern("MS", "MS"));
1148        assert!(!qualifier_matches_pattern("MS", "M")); // Not a prefix match after R-003
1149        // Wildcard patterns
1150        assert!(qualifier_matches_pattern("MS", "M*"));
1151        assert!(qualifier_matches_pattern("MRY", "M*Y"));
1152        assert!(!qualifier_matches_pattern("AB", "M*"));
1153    }
1154
1155    /// Comprehensive table-driven tests for `qualifier_matches_pattern`.
1156    #[test]
1157    fn qualifier_matches_pattern_table() {
1158        // (value, pattern, expected)
1159        let cases: &[(&str, &str, bool)] = &[
1160            // ── empty inputs ────────────────────────────────────────────────
1161            ("", "", true),        // empty matches empty
1162            ("", "*", true),       // wildcard matches empty string
1163            ("A", "", false),      // non-empty does not match empty pattern
1164            ("", "A", false),      // empty does not match non-empty literal
1165            // ── literal (no wildcard) ────────────────────────────────────────
1166            ("MS", "MS", true),
1167            ("BY", "BY", true),
1168            ("ms", "MS", false),   // case-sensitive
1169            ("MSX", "MS", false),  // prefix is NOT a match without wildcard
1170            ("M", "MS", false),    // too short
1171            // ── single wildcard at the end (prefix match) ────────────────────
1172            ("MS", "M*", true),
1173            ("MULTI", "MUL*", true),
1174            ("AB", "M*", false),
1175            ("", "M*", false),     // empty does not start with 'M'
1176            // ── single wildcard at the start (suffix match) ──────────────────
1177            ("MSG", "*G", true),
1178            ("G", "*G", true),
1179            ("MSG", "*X", false),
1180            ("", "*G", false),
1181            // ── wildcard in the middle ───────────────────────────────────────
1182            ("MRY", "M*Y", true),
1183            ("MAY", "M*Y", true),
1184            ("MY", "M*Y", true),   // zero-width wildcard: "M" + "" + "Y"
1185            ("MYY", "M*Y", true),  // last 'Y' matches, wildcard = 'Y'
1186            ("MAYZ", "M*Y", false),// does not end with 'Y'
1187            ("AB", "M*Y", false),
1188            // ── bare wildcard (match-all) ────────────────────────────────────
1189            ("*", "*", true),      // literal '*' value vs wildcard pattern
1190            ("anything", "*", true),
1191            ("", "*", true),
1192            // ── multiple wildcards ────────────────────────────────────────────
1193            ("ABCDE", "A*C*E", true),
1194            ("ACE", "A*C*E", true),  // zero-width wildcards
1195            ("AXCYE", "A*C*E", true),
1196            ("ABCDF", "A*C*E", false),
1197            // ── wildcard with empty segment between stars ─────────────────────
1198            ("AB", "A**B", true),   // "A**B" → parts ["A", "", "B"] → ends_with_wildcard?
1199            // ── pattern longer than value ─────────────────────────────────────
1200            ("AB", "A*B*C", false),
1201            // ── value contains pattern as substring but must anchor start ─────
1202            ("XMS", "MS", false),
1203        ];
1204
1205        for (value, pattern, expected) in cases {
1206            let got = qualifier_matches_pattern(value, pattern);
1207            assert_eq!(
1208                got, *expected,
1209                "qualifier_matches_pattern({value:?}, {pattern:?}) expected {expected} but got {got}"
1210            );
1211        }
1212    }
1213
1214    #[test]
1215    fn typed_qualifier_helpers_work() {
1216        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1217        let segments: Vec<Segment<'_>> =
1218            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1219
1220        let first = find_segment_typed::<NadM>(&segments).unwrap();
1221        assert_eq!(first.element_str(0), Some("MS"));
1222
1223        let all = find_segments_typed::<NadWildcard>(&segments);
1224        assert_eq!(all.len(), 2);
1225    }
1226
1227    #[test]
1228    fn segment_accessor_trait_methods_work() {
1229        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1230        let segments: Vec<Segment<'_>> =
1231            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1232        let seg = &segments[0];
1233
1234        assert_eq!(SegmentAccessor::get_element(seg, 1), Some("SENDER"));
1235        assert_eq!(SegmentAccessor::required_composite(seg, 0, 1).unwrap(), "1");
1236        let parsed: i32 = SegmentAccessor::code_element(seg, 4).unwrap();
1237        assert_eq!(parsed, 1);
1238        let reps = SegmentAccessor::repeating_components(seg, 3, 0, 2).unwrap();
1239        assert_eq!(reps, vec!["200101", "0900"]);
1240    }
1241
1242    #[test]
1243    fn group_helpers_detect_contiguity() {
1244        struct NadAny;
1245        impl EdifactSegmentTag for NadAny {
1246            const SEGMENT_TAG: &'static str = "NAD";
1247        }
1248
1249        let contiguous_input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'";
1250        let contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(contiguous_input)
1251            .collect::<Result<_, _>>()
1252            .unwrap();
1253        assert!(groups_are_contiguous_by_qualifier::<NadAny>(
1254            &contiguous_segments
1255        ));
1256
1257        let non_contiguous_input = b"NAD+MS+1'RFF+AA:1'NAD+MR+2'";
1258        let non_contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(non_contiguous_input)
1259            .collect::<Result<_, _>>()
1260            .unwrap();
1261        assert!(!groups_are_contiguous_by_qualifier::<NadAny>(
1262            &non_contiguous_segments
1263        ));
1264    }
1265
1266    #[test]
1267    fn group_helpers_collect_contiguous_groups() {
1268        struct NadAny;
1269        impl EdifactSegmentTag for NadAny {
1270            const SEGMENT_TAG: &'static str = "NAD";
1271        }
1272
1273        let input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'NAD+BY+3'";
1274        let segments: Vec<Segment<'_>> =
1275            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1276        let groups = contiguous_groups_by_qualifier::<NadAny>(&segments);
1277
1278        assert_eq!(groups.len(), 2);
1279        assert_eq!(groups[0].len(), 2);
1280        assert_eq!(groups[1].len(), 1);
1281    }
1282
1283    // ── MessageWindowsIter tests ──────────────────────────────────────────────
1284
1285    #[test]
1286    fn message_windows_bytes_yields_complete_windows() {
1287        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1288                      UNH+1+ORDERS:D:96A:UN'\
1289                      BGM+220+PO-001+9'\
1290                      UNT+3+1'\
1291                      UNZ+1+1'";
1292        let windows: Vec<_> = message_windows_bytes(input)
1293            .collect::<Result<_, _>>()
1294            .unwrap();
1295        assert_eq!(windows.len(), 1);
1296        assert_eq!(windows[0][0].tag, "UNH");
1297        assert_eq!(windows[0].last().unwrap().tag, "UNT");
1298    }
1299
1300    #[test]
1301    fn message_windows_truncated_stream_returns_error() {
1302        // Stream ends after UNH and BGM but without UNT — truncation must be an error
1303        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1304        let results: Vec<_> = message_windows_bytes(input).collect();
1305        assert_eq!(results.len(), 1);
1306        assert!(
1307            matches!(results[0], Err(EdifactError::UnexpectedEof { .. })),
1308            "expected UnexpectedEof for truncated window, got: {:?}",
1309            results[0]
1310        );
1311    }
1312
1313    #[test]
1314    fn message_windows_subsequent_calls_return_none_after_truncation() {
1315        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1316        let mut iter = message_windows_bytes(input);
1317        assert!(matches!(
1318            iter.next(),
1319            Some(Err(EdifactError::UnexpectedEof { .. }))
1320        ));
1321        // After the error, the iterator must be fused (done = true)
1322        assert!(iter.next().is_none());
1323    }
1324
1325    #[test]
1326    fn message_windows_unh_without_unt_before_next_unh_returns_error() {
1327        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'\
1328                      UNH+2+ORDERS:D:96A:UN'BGM+220+PO-002+9'UNT+3+2'";
1329        let results: Vec<_> = message_windows_bytes(input).collect();
1330        // First item must be an error (UNH before UNT)
1331        assert!(
1332            matches!(results[0], Err(EdifactError::ValidationFailed { .. })),
1333            "expected ValidationFailed, got: {:?}",
1334            results[0]
1335        );
1336    }
1337}