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::borrow::Cow;
10use std::io::Read;
11use std::str::FromStr;
12
13// ── traits ────────────────────────────────────────────────────────────────────
14
15/// Types that can be deserialized from a slice of EDIFACT segments.
16///
17/// Implement manually or derive with `#[derive(EdifactDeserialize)]` from the
18/// `edifact-rs-derive` crate.
19pub trait EdifactDeserialize: Sized {
20    /// Deserialize `Self` from the provided segment slice.
21    ///
22    /// The slice may contain any number of segments; implementations extract
23    /// only the ones they care about and ignore the rest.
24    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError>;
25
26    /// Deserialize `Self` from a slice of owned EDIFACT segments.
27    ///
28    /// # Default implementation
29    ///
30    /// Converts each [`crate::OwnedSegment`] to its borrowed form via
31    /// [`crate::OwnedSegment::as_borrowed`] and delegates to
32    /// [`edifact_deserialize`][Self::edifact_deserialize].  This incurs one
33    /// `Vec<Segment<'_>>` allocation per call.
34    ///
35    /// # Warning: allocation overhead
36    ///
37    /// This default implementation incurs one [`Vec<Segment<'_>>`](Vec)
38    /// allocation per call.  Types generated by `#[derive(EdifactDeserialize)]`
39    /// automatically override this method to work directly on the owned data
40    /// without the intermediate allocation.  Manual implementations should also
41    /// override when used in the high-throughput reader-streaming path
42    /// ([`deserialize_first_from_reader`], [`deserialize_all_from_reader`],
43    /// [`deserialize_messages_from_reader`]) to avoid the per-message allocation.
44    fn edifact_deserialize_owned(segments: &[crate::OwnedSegment]) -> Result<Self, EdifactError> {
45        let borrowed: Vec<Segment<'_>> = segments.iter().map(|s| s.as_borrowed()).collect();
46        Self::edifact_deserialize(&borrowed)
47    }
48}
49
50/// Types that can be deserialized from a composite EDIFACT element.
51///
52/// Implement this for custom composite structs used with
53/// `#[edifact(composite)]` in derive macros.
54pub trait EdifactCompositeDeserialize: Sized {
55    /// Deserialize `Self` from a composite element.
56    fn edifact_deserialize_composite(composite: CompositeElement<'_>)
57    -> Result<Self, EdifactError>;
58}
59
60impl EdifactCompositeDeserialize for Vec<String> {
61    fn edifact_deserialize_composite(
62        composite: CompositeElement<'_>,
63    ) -> Result<Self, EdifactError> {
64        Ok(composite.iter().map(str::to_owned).collect())
65    }
66}
67
68/// Companion trait that declares a type's segment tag (and optional qualifier).
69///
70/// Required for the `Vec<T>` blanket impl and for finding the right segment in
71/// a message-level struct deserialization.
72pub trait EdifactSegmentTag {
73    /// The 3-character EDIFACT segment tag (e.g. `"BGM"`, `"NAD"`).
74    const SEGMENT_TAG: &'static str;
75
76    /// Optional qualifier pattern to further constrain segment matching.
77    ///
78    /// Examples:
79    /// - `Some("MS")` for exact qualifier matching.
80    /// - `Some("M*")` for wildcard prefix matching (matches `"MS"`, `"MR"`, etc.).
81    const QUALIFIER_PATTERN: Option<&'static str> = None;
82
83    /// Return `true` if `seg`'s qualifier matches this type's qualifier pattern.
84    fn matches_qualifier(seg: &Segment<'_>) -> bool {
85        match Self::QUALIFIER_PATTERN {
86            Some(pattern) => seg
87                .element_str(0)
88                .is_some_and(|q| qualifier_matches_pattern(q, pattern)),
89            None => true,
90        }
91    }
92
93    /// Return `true` if `seg` is the segment this type maps to.
94    ///
95    /// Default: `seg.tag == Self::SEGMENT_TAG`.  Override to also match on a
96    /// qualifier (e.g. `NAD+BY` — element 0 = `"BY"`).
97    fn matches_segment(seg: &Segment<'_>) -> bool {
98        seg.tag == Self::SEGMENT_TAG && Self::matches_qualifier(seg)
99    }
100
101    /// Like [`matches_segment`][Self::matches_segment] but works directly on an
102    /// [`crate::OwnedSegment`] without incurring the `Vec` allocation of
103    /// [`crate::OwnedSegment::as_borrowed`].
104    fn matches_owned_segment(seg: &crate::OwnedSegment) -> bool {
105        if seg.tag != Self::SEGMENT_TAG {
106            return false;
107        }
108        match Self::QUALIFIER_PATTERN {
109            None => true,
110            Some(pattern) => {
111                let q = seg
112                    .elements
113                    .first()
114                    .and_then(|e| e.components.first())
115                    .map(|(c, _)| c.as_str())
116                    .unwrap_or("");
117                qualifier_matches_pattern(q, pattern)
118            }
119        }
120    }
121}
122
123// ── blanket impl for Vec<T> ───────────────────────────────────────────────────
124
125/// Deserializes each segment matching `T::matches_segment` as an independent
126/// single-segment slice, collecting the results.
127impl<T> EdifactDeserialize for Vec<T>
128where
129    T: EdifactDeserialize + EdifactSegmentTag,
130{
131    fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
132        segments
133            .iter()
134            .filter(|s| T::matches_segment(s))
135            .map(|seg| T::edifact_deserialize(std::slice::from_ref(seg)))
136            .collect()
137    }
138
139    fn edifact_deserialize_owned(segments: &[crate::OwnedSegment]) -> Result<Self, EdifactError> {
140        segments
141            .iter()
142            .filter(|s| T::matches_owned_segment(s))
143            .map(|seg| T::edifact_deserialize_owned(std::slice::from_ref(seg)))
144            .collect()
145    }
146}
147
148// ── public API ────────────────────────────────────────────────────────────────
149
150/// Deserialize a value of type `T` from EDIFACT bytes.
151///
152/// Unlike [`crate::from_bytes`], which parses bytes into raw [`Segment`]s, this
153/// function fully deserializes the payload into a typed Rust value via [`EdifactDeserialize`].
154///
155/// # Memory
156///
157/// This function buffers **all** parsed segments into a `Vec<Segment<'_>>` before
158/// calling `T::edifact_deserialize`.  For large interchanges — or when only the first
159/// matching segment is needed — prefer [`deserialize_first_streaming`] or
160/// [`deserialize_all_streaming`] to avoid holding the entire input in memory.
161/// For reader-based I/O with bounded memory use, see [`deserialize_first_from_reader`]
162/// and [`deserialize_all_from_reader`].
163pub fn deserialize<T: EdifactDeserialize>(input: &[u8]) -> Result<T, EdifactError> {
164    let segments: Vec<Segment<'_>> = crate::from_bytes(input).collect::<Result<_, _>>()?;
165    T::edifact_deserialize(&segments)
166}
167
168/// Stream-parse EDIFACT bytes and deserialize the first matching segment as `T`.
169///
170/// This avoids allocating a full `Vec<Segment>` and is intended for low-memory
171/// extraction of segment-scoped types.
172pub fn deserialize_first_streaming<T>(input: &[u8]) -> Result<T, EdifactError>
173where
174    T: EdifactDeserialize + EdifactSegmentTag,
175{
176    for segment in crate::from_bytes(input) {
177        let segment = segment?;
178        if T::matches_segment(&segment) {
179            return T::edifact_deserialize(std::slice::from_ref(&segment));
180        }
181    }
182
183    Err(EdifactError::MissingSegment {
184        tag: T::SEGMENT_TAG.to_owned(),
185        expected_position: "any position in input".to_owned(),
186    })
187}
188
189/// Stream-parse EDIFACT bytes and deserialize all matching segments as `Vec<T>`.
190///
191/// This avoids buffering non-matching segments in memory.
192pub fn deserialize_all_streaming<T>(input: &[u8]) -> Result<Vec<T>, EdifactError>
193where
194    T: EdifactDeserialize + EdifactSegmentTag,
195{
196    let mut out = Vec::new();
197    for segment in crate::from_bytes(input) {
198        let segment = segment?;
199        if T::matches_segment(&segment) {
200            out.push(T::edifact_deserialize(std::slice::from_ref(&segment))?);
201        }
202    }
203    Ok(out)
204}
205
206/// Stream-parse EDIFACT from a reader and deserialize the first matching segment as `T`.
207///
208/// This is the low-memory typed path for large payloads read from I/O streams.
209pub fn deserialize_first_from_reader<T, R>(reader: R) -> Result<T, EdifactError>
210where
211    T: EdifactDeserialize + EdifactSegmentTag,
212    R: Read,
213{
214    for segment in crate::from_reader(reader) {
215        let segment = segment?;
216        // O(1) tag + qualifier check before paying for as_borrowed().
217        if !T::matches_owned_segment(&segment) {
218            continue;
219        }
220        return T::edifact_deserialize_owned(std::slice::from_ref(&segment));
221    }
222
223    Err(EdifactError::MissingSegment {
224        tag: T::SEGMENT_TAG.to_owned(),
225        expected_position: "any position in input".to_owned(),
226    })
227}
228
229/// Stream-parse EDIFACT from a reader and deserialize all matching segments as `Vec<T>`.
230pub fn deserialize_all_from_reader<T, R>(reader: R) -> Result<Vec<T>, EdifactError>
231where
232    T: EdifactDeserialize + EdifactSegmentTag,
233    R: Read,
234{
235    let mut out = Vec::new();
236    for segment in crate::from_reader(reader) {
237        let segment = segment?;
238        // O(1) tag + qualifier check before paying for as_borrowed().
239        if !T::matches_owned_segment(&segment) {
240            continue;
241        }
242        out.push(T::edifact_deserialize_owned(std::slice::from_ref(
243            &segment,
244        ))?);
245    }
246    Ok(out)
247}
248
249/// Deserialize a value of type `T` from an EDIFACT string.
250pub fn deserialize_str<T: EdifactDeserialize>(input: &str) -> Result<T, EdifactError> {
251    deserialize(input.as_bytes())
252}
253
254// ── helper functions ──────────────────────────────────────────────────────────
255
256/// Find the first segment with the given tag.
257pub fn find_segment<'s, 'd>(segments: &'s [Segment<'d>], tag: &str) -> Option<&'s Segment<'d>> {
258    segments.iter().find(|s| s.tag == tag)
259}
260
261/// Iterate over all segments with the given tag without allocating a `Vec`.
262pub fn find_segments_iter<'s, 'd: 's>(
263    segments: &'s [Segment<'d>],
264    tag: &'s str,
265) -> impl Iterator<Item = &'s Segment<'d>> {
266    segments.iter().filter(move |s| s.tag == tag)
267}
268
269/// Find the first segment matching `tag` whose element 0 equals `qualifier`.
270pub fn find_qualified_segment<'s, 'd>(
271    segments: &'s [Segment<'d>],
272    tag: &str,
273    qualifier: &str,
274) -> Option<&'s Segment<'d>> {
275    segments
276        .iter()
277        .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
278}
279
280/// Find the first segment by type-level qualifier pattern.
281pub fn find_segment_typed<'s, 'd, T>(segments: &'s [Segment<'d>]) -> Option<&'s Segment<'d>>
282where
283    T: EdifactSegmentTag,
284{
285    segments.iter().find(|s| T::matches_segment(s))
286}
287
288/// Iterate over all segments by type-level qualifier pattern.
289pub fn find_segments_typed<'s, 'd: 's, T>(
290    segments: &'s [Segment<'d>],
291) -> impl Iterator<Item = &'s Segment<'d>>
292where
293    T: EdifactSegmentTag,
294{
295    segments.iter().filter(|s| T::matches_segment(s))
296}
297
298/// Collect contiguous groups of segments that match `T`.
299///
300/// Each group is a borrowed slice of the original `segments` array.
301/// Use [`contiguous_groups_iter`] to avoid the outer `Vec` allocation.
302pub fn contiguous_groups_by_qualifier<'s, 'd, T>(
303    segments: &'s [Segment<'d>],
304) -> Vec<&'s [Segment<'d>]>
305where
306    T: EdifactSegmentTag,
307{
308    let mut groups = Vec::new();
309    let mut idx = 0;
310    while idx < segments.len() {
311        if T::matches_segment(&segments[idx]) {
312            let start = idx;
313            idx += 1;
314            while idx < segments.len() && T::matches_segment(&segments[idx]) {
315                idx += 1;
316            }
317            groups.push(&segments[start..idx]);
318        } else {
319            idx += 1;
320        }
321    }
322    groups
323}
324
325/// Iterate lazily over contiguous groups of segments that match `T`.
326///
327/// Each yielded item is a borrowed slice `&[Segment<'_>]` that forms one
328/// contiguous run of `T`-matching segments.  No outer `Vec` is allocated —
329/// the caller can break early or collect only as many groups as needed.
330///
331/// This function uses separate lifetimes for the slice reference (`'s`) and
332/// the segment data (`'d`), matching the signature of
333/// [`contiguous_groups_by_qualifier`].
334///
335/// # Example
336/// ```rust,ignore
337/// for group in contiguous_groups_iter::<UnaSegment>(&segments) {
338///     process_group(group);
339/// }
340/// ```
341pub fn contiguous_groups_iter<'s, 'd, T>(
342    segments: &'s [Segment<'d>],
343) -> impl Iterator<Item = &'s [Segment<'d>]> + 's
344where
345    T: EdifactSegmentTag,
346{
347    let mut idx = 0;
348    let len = segments.len();
349    std::iter::from_fn(move || {
350        // Skip non-matching segments
351        while idx < len && !T::matches_segment(&segments[idx]) {
352            idx += 1;
353        }
354        if idx >= len {
355            return None;
356        }
357        let start = idx;
358        idx += 1;
359        while idx < len && T::matches_segment(&segments[idx]) {
360            idx += 1;
361        }
362        Some(&segments[start..idx])
363    })
364}
365
366/// Return `true` if all segments matching `T` are in one contiguous block.
367pub fn groups_are_contiguous_by_qualifier<T>(segments: &[Segment<'_>]) -> bool
368where
369    T: EdifactSegmentTag,
370{
371    let mut seen_match = false;
372    let mut seen_gap_after_match = false;
373
374    for seg in segments {
375        if T::matches_segment(seg) {
376            if seen_gap_after_match {
377                return false;
378            }
379            seen_match = true;
380        } else if seen_match {
381            seen_gap_after_match = true;
382        }
383    }
384
385    true
386}
387
388/// Match a qualifier value against an exact or wildcard pattern.
389///
390/// Rules:
391/// - If `pattern` contains `*`, it is treated as a glob wildcard (e.g. `"M*"` matches `"MS"`, `"MR"`).
392/// - If no wildcard is present, exact match is required.
393///
394/// Prefix matching without an explicit `*` was deliberately removed: `"M"` matches only `"M"`,
395/// not `"MS"` or `"MR"`.  Use `"M*"` for prefix semantics.
396///
397/// Patterns with more than 3 wildcard segments (i.e. 4 or more `*` characters) are rejected
398/// immediately with `false` to guard against pathological O(n·m) matching.
399pub fn qualifier_matches_pattern(value: &str, pattern: &str) -> bool {
400    if pattern.is_empty() {
401        return value.is_empty();
402    }
403
404    if !pattern.contains('*') {
405        return value == pattern;
406    }
407
408    // Fast path: single wildcard (dominant case — e.g. "M*" or "*:MS").
409    // The length test is what stops the prefix and the suffix from overlapping:
410    // `value.len() >= prefix.len() + suffix.len()` is exactly the condition that
411    // leaves a (possibly empty) gap between them for `*` to cover.
412    if let Some((prefix, suffix)) = pattern.split_once('*') {
413        if !suffix.contains('*') {
414            return value.len() >= prefix.len() + suffix.len()
415                && value.starts_with(prefix)
416                && value.ends_with(suffix);
417        }
418    }
419
420    // General multi-wildcard path.
421    let parts: smallvec::SmallVec<[&str; 4]> = pattern.split('*').collect();
422
423    // Guard against pathological O(n·m) matching on adversarial patterns.
424    // EDIFACT qualifier patterns use at most 1–2 wildcards; 4 is a generous
425    // ceiling. Anything beyond is almost certainly a programming error or
426    // adversarial input — reject immediately.
427    if parts.len() > 4 {
428        return false;
429    }
430
431    let prefix = parts[0];
432    let suffix = parts[parts.len() - 1];
433
434    if !value.starts_with(prefix) || !value.ends_with(suffix) {
435        return false;
436    }
437
438    let mid_start = prefix.len();
439    let mid_end = value.len().saturating_sub(suffix.len());
440
441    if mid_start > mid_end {
442        return parts[1..parts.len() - 1].iter().all(|p| p.is_empty());
443    }
444
445    let mut remaining = &value[mid_start..mid_end];
446
447    for part in &parts[1..parts.len() - 1] {
448        if part.is_empty() {
449            continue;
450        }
451        match remaining.find(part) {
452            Some(idx) => remaining = &remaining[idx + part.len()..],
453            None => return false,
454        }
455    }
456
457    true
458}
459
460/// Extract the string value of element `idx` from `seg`, or `""` if absent.
461#[inline]
462pub fn element_str<'s>(seg: &'s Segment<'_>, idx: usize) -> &'s str {
463    seg.element_str(idx).unwrap_or("")
464}
465
466// ── segment accessor helpers ───────────────────────────────────────────────────
467
468/// Extract a required text element from a segment.
469///
470/// Returns the element's first component, or an error if absent or empty.
471///
472/// # Empty-string semantics
473///
474/// EDIFACT allows elements to be syntactically present but carry an empty
475/// string value (e.g., `SEG++'`). This function treats an empty string as
476/// *absent* — it returns [`EdifactError::MissingRequiredElement`] in that
477/// case, matching the EDIFACT rule that mandatory data elements must carry
478/// a non-empty value.
479///
480/// Delegates to [`SegmentAccessor::text_element`].
481pub fn required_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Result<&'a str, EdifactError> {
482    seg.text_element(idx)
483}
484
485/// Extract an optional text element from a segment.
486///
487/// Returns the element's first component, or None if absent or empty.
488///
489/// Delegates to [`SegmentAccessor::optional_element`].
490pub fn optional_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Option<&'a str> {
491    SegmentAccessor::optional_element(seg, idx)
492}
493
494/// Extract a required component from a segment element.
495///
496/// Returns the component value, or an error if the element or component is absent.
497///
498/// # Empty-string semantics
499///
500/// Like [`required_element`], an empty string component value is treated as
501/// *absent*.  A component that is syntactically present as `''` (two
502/// consecutive component separators) will cause this function to return
503/// [`EdifactError::MissingRequiredComponent`].
504///
505/// # Failure modes
506///
507/// - [`EdifactError::MissingRequiredElement`] — element `elem_idx` is absent.
508/// - [`EdifactError::MissingRequiredComponent`] — element is present but component `comp_idx` is absent or empty.
509///
510/// Delegates to [`SegmentAccessor::required_composite`].
511pub fn required_component<'a>(
512    seg: &'a Segment<'_>,
513    elem_idx: usize,
514    comp_idx: usize,
515) -> Result<&'a str, EdifactError> {
516    seg.required_composite(elem_idx, comp_idx)
517}
518
519/// Extract an optional component from a segment element.
520///
521/// Returns the component value, or None if absent or empty.
522///
523/// Delegates to [`SegmentAccessor::get_component`].
524pub fn optional_component<'a>(
525    seg: &'a Segment<'_>,
526    elem_idx: usize,
527    comp_idx: usize,
528) -> Option<&'a str> {
529    SegmentAccessor::get_component(seg, elem_idx, comp_idx)
530}
531
532/// Iterate over all components of an element without allocating a `Vec`.
533///
534/// Yields an empty iterator if the element is absent.
535pub fn get_components_iter<'a>(seg: &'a Segment<'_>, idx: usize) -> impl Iterator<Item = &'a str> {
536    seg.elements
537        .get(idx)
538        .into_iter()
539        .flat_map(|elem| elem.components.iter().map(|(c, _)| c.as_ref()))
540}
541
542/// Iterate one component across **every repetition** of a data element
543/// (ISO 9735-1 §8.6).
544///
545/// This is the read side of a repeating data element: `RFF+ON:1*ON:2` has one
546/// element with two occurrences, and asking for component 1 yields `"1"` then
547/// `"2"`. [`get_components_iter`] answers the different question — the
548/// components *within* one occurrence.
549///
550/// Every occurrence produces exactly one item, using `""` where that occurrence
551/// omits the component. §8.7.3 makes the position of an occurrence significant —
552/// `DE*DE***DE` deliberately transfers two empty ones — so dropping them would
553/// shift every later value into the wrong slot.
554///
555/// Yields nothing when the element is absent.
556///
557/// # Example
558///
559/// ```
560/// use edifact_rs::{from_bytes, repeated_components};
561///
562/// // `UNA` position 050 declares `*` as the repetition separator.
563/// let segments: Vec<_> = from_bytes(b"UNA:+.?*'RFF+ON:1*ON:2*ON:3'")
564///     .collect::<Result<Vec<_>, _>>()?;
565///
566/// let references: Vec<&str> = repeated_components(&segments[0], 0, 1).collect();
567/// assert_eq!(references, ["1", "2", "3"]);
568///
569/// // Component 0 is the qualifier, repeated in every occurrence.
570/// let qualifiers: Vec<&str> = repeated_components(&segments[0], 0, 0).collect();
571/// assert_eq!(qualifiers, ["ON", "ON", "ON"]);
572/// # Ok::<(), edifact_rs::EdifactError>(())
573/// ```
574pub fn repeated_components<'a>(
575    seg: &'a Segment<'_>,
576    element: usize,
577    component: usize,
578) -> impl Iterator<Item = &'a str> {
579    seg.elements.get(element).into_iter().flat_map(move |elem| {
580        elem.repetitions()
581            .map(move |occurrence| occurrence.get(component).map_or("", |(c, _)| c.as_ref()))
582    })
583}
584
585/// Owned-segment counterpart of [`repeated_components`].
586pub fn repeated_components_owned(
587    seg: &crate::OwnedSegment,
588    element: usize,
589    component: usize,
590) -> impl Iterator<Item = &str> {
591    seg.elements.get(element).into_iter().flat_map(move |elem| {
592        elem.repetitions()
593            .map(move |occurrence| occurrence.get(component).map_or("", |(c, _)| c.as_str()))
594    })
595}
596
597/// A composite data element wrapper for clearer ergonomics.
598///
599/// Holds borrowed `&'a str` references to the underlying data — no string
600/// copies are made.  Up to 4 component pointers are stored inline (via
601/// [`SmallVec`]) so the common case is fully allocation-free.
602///
603/// The lifetime `'a` represents the underlying data lifetime.
604///
605/// [`SmallVec`]: smallvec::SmallVec
606pub struct CompositeElement<'a> {
607    components: smallvec::SmallVec<[&'a str; 4]>,
608}
609
610impl<'a> CompositeElement<'a> {
611    /// Create a `CompositeElement` from a pre-existing `Cow` component slice.
612    ///
613    /// Used internally by generated owned-deserialization code.
614    pub fn from_slice(components: &'a [std::borrow::Cow<'a, str>]) -> Self {
615        Self {
616            components: components.iter().map(|c| c.as_ref()).collect(),
617        }
618    }
619
620    /// Crate-private constructor for direct `&str` components.
621    pub(crate) fn from_strs(components: smallvec::SmallVec<[&'a str; 4]>) -> Self {
622        Self { components }
623    }
624
625    /// Get the component at index `i`, or None if absent.
626    pub fn get(&self, i: usize) -> Option<&'a str> {
627        self.components.get(i).copied()
628    }
629
630    /// Get the component at index `i`, or empty string if absent.
631    pub fn get_or_empty(&self, i: usize) -> &'a str {
632        self.get(i).unwrap_or("")
633    }
634
635    /// Get the number of components.
636    pub fn len(&self) -> usize {
637        self.components.len()
638    }
639
640    /// Check if the composite is empty.
641    pub fn is_empty(&self) -> bool {
642        self.components.is_empty()
643    }
644
645    /// Iterate over all component string values.
646    pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
647        self.components.iter().copied()
648    }
649}
650
651/// Get a composite element from a segment with clearer ergonomics.
652pub fn composite_element<'a, 'd: 'a>(
653    seg: &'a Segment<'d>,
654    idx: usize,
655) -> Option<CompositeElement<'a>> {
656    // `.collect()` into `SmallVec<[&str; 4]>` keeps ≤4-component elements
657    // fully on the stack (no heap allocation for the common case).
658    seg.elements.get(idx).map(|elem| {
659        CompositeElement::from_strs(elem.components.iter().map(|(c, _)| c.as_ref()).collect())
660    })
661}
662
663/// Find the first [`OwnedSegment`] with the given tag.
664///
665/// Zero-allocation counterpart of [`find_segment`] for use in
666/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
667///
668/// [`OwnedSegment`]: crate::OwnedSegment
669pub fn find_segment_owned<'s>(
670    segments: &'s [crate::OwnedSegment],
671    tag: &str,
672) -> Option<&'s crate::OwnedSegment> {
673    segments.iter().find(|s| s.tag == tag)
674}
675
676/// Find the first [`OwnedSegment`] with the given tag **and** qualifier.
677///
678/// The qualifier is compared against the first component of element 0.
679/// Zero-allocation counterpart of [`find_qualified_segment`] for use in
680/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
681///
682/// [`OwnedSegment`]: crate::OwnedSegment
683pub fn find_qualified_segment_owned<'s>(
684    segments: &'s [crate::OwnedSegment],
685    tag: &str,
686    qualifier: &str,
687) -> Option<&'s crate::OwnedSegment> {
688    segments
689        .iter()
690        .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
691}
692
693/// Segment accessor trait for ergonomic typed extraction.
694pub trait SegmentAccessor<'a> {
695    /// Get non-empty element text at index `idx`.
696    fn get_element(&'a self, idx: usize) -> Option<&'a str>;
697    /// Get non-empty component text at element/component indexes.
698    fn get_component(&'a self, elem: usize, comp: usize) -> Option<&'a str>;
699    /// Get a composite wrapper for element `idx`.
700    fn get_composite(&'a self, idx: usize) -> Option<CompositeElement<'a>>;
701
702    /// Get required non-empty element text.
703    fn text_element(&'a self, idx: usize) -> Result<&'a str, EdifactError>;
704    /// Get optional non-empty element text.
705    fn optional_element(&'a self, idx: usize) -> Option<&'a str>;
706    /// Parse a typed code value from a required element.
707    fn code_element<T: FromStr>(&'a self, idx: usize) -> Result<T, EdifactError>;
708    /// Get required non-empty composite component.
709    fn required_composite(&'a self, elem: usize, comp: usize) -> Result<&'a str, EdifactError>;
710    /// Get `count` required components starting at `start_idx` from element `elem`.
711    ///
712    /// This walks *components inside one data element* — the `:`-separated parts
713    /// of a composite. It has nothing to do with ISO 9735-4 repeating data
714    /// elements; for those, read
715    /// [`Element::repetitions`][crate::Element::repetitions].
716    ///
717    /// Allocates a `Vec`.  For a zero-alloc alternative, use
718    /// [`component_range_iter`][Self::component_range_iter] and
719    /// consume the iterator directly without collecting.
720    fn component_range(
721        &'a self,
722        elem: usize,
723        start_idx: usize,
724        count: usize,
725    ) -> Result<Vec<&'a str>, EdifactError> {
726        // Default implementation delegates to the zero-alloc iterator and
727        // collects.  Implementors that can do better should override this.
728        self.component_range_iter(elem, start_idx, count).collect()
729    }
730
731    /// Iterate over `count` required components starting at `start_idx` from element `elem`.
732    ///
733    /// Allocation-free alternative to [`component_range`][Self::component_range];
734    /// the caller supplies the iteration budget and consumes results on the fly.
735    fn component_range_iter(
736        &'a self,
737        elem: usize,
738        start_idx: usize,
739        count: usize,
740    ) -> impl Iterator<Item = Result<&'a str, EdifactError>> + 'a;
741}
742
743impl<'s, 'd> SegmentAccessor<'s> for Segment<'d>
744where
745    'd: 's,
746{
747    fn get_element(&'s self, idx: usize) -> Option<&'s str> {
748        self.element_str(idx).filter(|s| !s.is_empty())
749    }
750
751    fn get_component(&'s self, elem: usize, comp: usize) -> Option<&'s str> {
752        self.elements
753            .get(elem)
754            .and_then(|e| e.get_component(comp))
755            .filter(|s| !s.is_empty())
756    }
757
758    fn get_composite(&'s self, idx: usize) -> Option<CompositeElement<'s>> {
759        composite_element(self, idx)
760    }
761
762    fn text_element(&'s self, idx: usize) -> Result<&'s str, EdifactError> {
763        <Self as SegmentAccessor>::get_element(self, idx).ok_or_else(|| {
764            EdifactError::MissingRequiredElement {
765                tag: self.tag.to_owned(),
766                element_index: idx,
767            }
768        })
769    }
770
771    fn optional_element(&'s self, idx: usize) -> Option<&'s str> {
772        <Self as SegmentAccessor>::get_element(self, idx)
773    }
774
775    fn code_element<T: FromStr>(&'s self, idx: usize) -> Result<T, EdifactError> {
776        let raw = self.text_element(idx)?;
777        raw.parse::<T>().map_err(|_| EdifactError::InvalidText {
778            offset: self
779                .element_span(idx)
780                .map(|s| s.start)
781                .unwrap_or(self.span.start),
782        })
783    }
784
785    fn required_composite(&'s self, elem: usize, comp: usize) -> Result<&'s str, EdifactError> {
786        match self.elements.get(elem) {
787            None => Err(EdifactError::MissingRequiredElement {
788                tag: self.tag.to_owned(),
789                element_index: elem,
790            }),
791            Some(e) => e
792                .get_component(comp)
793                .filter(|s| !s.is_empty())
794                .ok_or_else(|| EdifactError::MissingRequiredComponent {
795                    tag: self.tag.to_owned(),
796                    element_index: elem,
797                    component_index: comp,
798                }),
799        }
800    }
801
802    fn component_range_iter(
803        &'s self,
804        elem: usize,
805        start_idx: usize,
806        count: usize,
807    ) -> impl Iterator<Item = Result<&'s str, EdifactError>> + 's {
808        let tag = self.tag;
809        let element_exists = self.elements.get(elem).is_some();
810        let components = self
811            .elements
812            .get(elem)
813            .map(|e| e.components.as_slice())
814            .unwrap_or(&[]);
815        (start_idx..start_idx + count).map(move |idx| {
816            components
817                .get(idx)
818                .map(|(c, _)| c.as_ref())
819                .filter(|s| !s.is_empty())
820                .ok_or_else(|| {
821                    if element_exists {
822                        EdifactError::MissingRequiredComponent {
823                            tag: tag.to_owned(),
824                            element_index: elem,
825                            component_index: idx,
826                        }
827                    } else {
828                        EdifactError::MissingRequiredElement {
829                            tag: tag.to_owned(),
830                            element_index: elem,
831                        }
832                    }
833                })
834        })
835    }
836}
837
838// ── message-window streaming ──────────────────────────────────────────────────
839
840/// A complete `UNH..UNT` message window that borrows from the original input.
841///
842/// Produced by [`MessageWindowsSliceIter`] / [`message_windows_bytes`].
843/// The `message_type` and `association_code` fields are extracted from the
844/// `UNH` segment at construction time, so callers do not need to traverse the
845/// segment list themselves.
846///
847/// `segments` contains the full window including the `UNH` and `UNT` service
848/// segments so that envelope-aware consumers have access to them.
849///
850/// # Accessing segments
851///
852/// ```rust,ignore
853/// for window in message_windows_bytes(input) {
854///     let window = window?;
855///     println!("type={:?} code={:?}", window.message_type, window.association_code);
856///     let bgm = window.segments.iter().find(|s| s.tag == "BGM");
857/// }
858/// ```
859#[derive(Debug)]
860pub struct MessageWindow<'a> {
861    /// EDIFACT message type extracted from `UNH` element 1, component 0.
862    ///
863    /// Borrowed when the component can be referenced directly, owned when
864    /// release-character unescaping requires allocation.
865    pub message_type: Option<Cow<'a, str>>,
866    /// Association-assigned code (DE 0057) from `UNH` element 1, component 4.
867    ///
868    /// Borrowed when the component can be referenced directly, owned when
869    /// release-character unescaping requires allocation.
870    pub association_code: Option<Cow<'a, str>>,
871    /// All segments in this window, from `UNH` through `UNT` (inclusive).
872    pub segments: Vec<crate::Segment<'a>>,
873}
874
875impl<'a> MessageWindow<'a> {
876    /// The message **body**: everything between `UNH` and `UNT`, exclusive.
877    ///
878    /// [`segments`][Self::segments] deliberately includes the service segments so
879    /// that envelope-aware consumers can read them, but they are exactly what a
880    /// body-oriented pass does not want.  In particular
881    /// [`group_segments_indexed`][crate::group_segments_indexed] is driven by
882    /// trigger tags alone, so a trailing `UNT` lands inside whichever group ran
883    /// last — pass `body()` and it cannot.
884    ///
885    /// Missing service segments are tolerated: a window that somehow lacks its
886    /// `UNH` or `UNT` yields whatever it does have, rather than panicking.
887    ///
888    /// # Example
889    ///
890    /// ```
891    /// use edifact_rs::from_bytes_windows;
892    ///
893    /// let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'UNT+3+1'";
894    /// let windows: Vec<_> = from_bytes_windows(input).collect::<Result<Vec<_>, _>>()?;
895    ///
896    /// assert_eq!(windows[0].segments.len(), 3);      // UNH, BGM, UNT
897    /// assert_eq!(
898    ///     windows[0].body().iter().map(|s| s.tag).collect::<Vec<_>>(),
899    ///     ["BGM"],
900    /// );
901    /// # Ok::<(), edifact_rs::EdifactError>(())
902    /// ```
903    #[must_use]
904    pub fn body(&self) -> &[crate::Segment<'a>] {
905        body_of(&self.segments, |segment| segment.tag)
906    }
907
908    /// Build a `MessageWindow` from a completed segment buffer.
909    ///
910    /// Extracts `message_type` and `association_code` from the leading `UNH`
911    /// segment.  Metadata extraction is allocation-free for borrowed components;
912    /// release-character unescaping may allocate owned strings when necessary.
913    fn from_segments(segments: Vec<crate::Segment<'a>>) -> Self {
914        let message_type = segments
915            .first()
916            .filter(|s| s.tag == "UNH")
917            .and_then(|unh| unh_component(unh, 0));
918        let association_code = segments
919            .first()
920            .filter(|s| s.tag == "UNH")
921            .and_then(|unh| unh_component(unh, 4));
922        Self {
923            message_type,
924            association_code,
925            segments,
926        }
927    }
928}
929
930/// Extract a non-empty string component from UNH element 1, preserving the
931/// component's borrowed/owned state.
932///
933/// By using two distinct lifetime parameters (`'b` for the borrow of `seg`,
934/// `'a` for the segment data), we tell the borrow checker that the returned
935/// `&'a str` lives independently of how long we hold `&seg`, which lets callers
936/// move `seg` into a containing struct after this call returns.
937fn unh_component<'a, 'b>(seg: &'b crate::Segment<'a>, comp_idx: usize) -> Option<Cow<'a, str>>
938where
939    'a: 'b,
940{
941    seg.elements
942        .get(1)
943        .and_then(|e| e.components.get(comp_idx))
944        .and_then(|(c, _)| if c.is_empty() { None } else { Some(c.clone()) })
945}
946
947/// Strip a leading `UNH` and a trailing `UNT` from a window's segments.
948///
949/// Shared by the borrowed and owned windows so the two cannot disagree about
950/// what "the body" means.
951fn body_of<S>(segments: &[S], tag: impl Fn(&S) -> &str) -> &[S] {
952    let start = usize::from(segments.first().is_some_and(|s| tag(s) == "UNH"));
953    let end = segments.len().saturating_sub(usize::from(
954        segments.last().is_some_and(|s| tag(s) == "UNT"),
955    ));
956    segments.get(start..end).unwrap_or(&[])
957}
958
959/// An owned, heap-allocated `UNH..UNT` message window.
960///
961/// Produced by [`MessageWindowsIter`] / [`message_windows_from_reader`].
962/// Equivalent to [`MessageWindow`] but with all data owned, so it outlives
963/// the original reader.
964///
965/// `segments` contains the full window including the `UNH` and `UNT` service
966/// segments.
967#[derive(Debug, Clone)]
968pub struct OwnedMessageWindow {
969    /// EDIFACT message type extracted from `UNH` element 1, component 0.
970    pub message_type: Option<String>,
971    /// Association-assigned code (DE 0057) from `UNH` element 1, component 4.
972    pub association_code: Option<String>,
973    /// All segments in this window, from `UNH` through `UNT` (inclusive).
974    pub segments: Vec<crate::OwnedSegment>,
975}
976
977impl OwnedMessageWindow {
978    /// The message body: everything between `UNH` and `UNT`, exclusive.
979    ///
980    /// The owned counterpart of [`MessageWindow::body`].
981    #[must_use]
982    pub fn body(&self) -> &[crate::OwnedSegment] {
983        body_of(&self.segments, |segment| segment.tag.as_str())
984    }
985
986    fn from_segments(segments: Vec<crate::OwnedSegment>) -> Self {
987        let unh = segments.first().filter(|s| s.tag == "UNH");
988        let message_type = unh
989            .and_then(|s| s.elements.get(1))
990            .and_then(|e| e.components.first())
991            .map(|(c, _)| c.as_str())
992            .filter(|s| !s.is_empty())
993            .map(str::to_owned);
994        let association_code = unh
995            .and_then(|s| s.elements.get(1))
996            .and_then(|e| e.components.get(4))
997            .map(|(c, _)| c.as_str())
998            .filter(|s| !s.is_empty())
999            .map(str::to_owned);
1000        Self {
1001            message_type,
1002            association_code,
1003            segments,
1004        }
1005    }
1006}
1007
1008/// An iterator that groups borrowed EDIFACT segments into per-message windows.
1009///
1010/// Zero-copy counterpart to [`MessageWindowsIter`] for in-memory byte slices.
1011/// Text content borrows from the original input; segment structure allocates
1012/// element vectors during parsing. Release-character unescaping may further
1013/// allocate owned strings when escape sequences are present. Envelope segments
1014/// outside a `UNH..UNT` pair are silently skipped.
1015///
1016/// Obtain this via [`message_windows_bytes`].
1017pub struct MessageWindowsSliceIter<'a> {
1018    inner: crate::FromBytesIter<'a>,
1019    buf: Vec<crate::Segment<'a>>,
1020    in_message: bool,
1021    done: bool,
1022}
1023
1024impl<'a> MessageWindowsSliceIter<'a> {
1025    fn new(inner: crate::FromBytesIter<'a>) -> Self {
1026        Self {
1027            inner,
1028            buf: Vec::new(),
1029            in_message: false,
1030            done: false,
1031        }
1032    }
1033}
1034
1035impl<'a> Iterator for MessageWindowsSliceIter<'a> {
1036    type Item = Result<MessageWindow<'a>, EdifactError>;
1037
1038    fn next(&mut self) -> Option<Self::Item> {
1039        if self.done {
1040            return None;
1041        }
1042        loop {
1043            let segment = match self.inner.next() {
1044                Some(Ok(s)) => s,
1045                Some(Err(e)) => {
1046                    self.done = true;
1047                    return Some(Err(e));
1048                }
1049                None => {
1050                    self.done = true;
1051                    if self.in_message && !self.buf.is_empty() {
1052                        self.in_message = false;
1053                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
1054                        return Some(Err(EdifactError::UnexpectedEof { offset }));
1055                    }
1056                    return None;
1057                }
1058            };
1059
1060            match segment.tag {
1061                "UNH" => {
1062                    if self.in_message {
1063                        self.buf.clear();
1064                        self.in_message = false;
1065                        self.done = true;
1066                        return Some(Err(EdifactError::InvalidSegmentForMessage {
1067                            tag: "UNH".to_owned(),
1068                            message_type: "ENVELOPE".to_owned(),
1069                            span: segment.span,
1070                        }));
1071                    }
1072                    self.buf.clear();
1073                    self.in_message = true;
1074                    self.buf.push(segment);
1075                }
1076                "UNT" if self.in_message => {
1077                    self.buf.push(segment);
1078                    self.in_message = false;
1079                    let segments = std::mem::take(&mut self.buf);
1080                    return Some(Ok(MessageWindow::from_segments(segments)));
1081                }
1082                _ if self.in_message => {
1083                    self.buf.push(segment);
1084                }
1085                _ => {
1086                    // Envelope segment outside a window — skip.
1087                }
1088            }
1089        }
1090    }
1091}
1092
1093/// An iterator that groups owned EDIFACT segments into per-message windows.
1094///
1095/// Each yielded item is an [`OwnedMessageWindow`] containing the segments for one
1096/// complete `UNH..UNT` message, inclusive of both service segments.
1097/// Envelope-level segments (`UNB`, `UNG`, `UNZ`, `UNE`) that sit outside any
1098/// `UNH..UNT` pair are silently skipped.
1099///
1100/// # Errors
1101///
1102/// - An inner-iterator error is forwarded immediately and iteration stops.
1103/// - A `UNH` seen while a prior window is still open (missing `UNT`) is an error.
1104/// - Input that ends while a `UNH` window is open (stream truncation) yields
1105///   `Err(EdifactError::UnexpectedEof { … })` before returning `None`.
1106///
1107/// # Construction
1108///
1109/// Use [`message_windows_from_reader`] or [`message_windows_bytes`] to
1110/// obtain a `MessageWindowsIter` directly.  For fully custom sources, call
1111/// [`MessageWindowsIter::new`] with any `Iterator<Item = Result<OwnedSegment,
1112/// EdifactError>>`.
1113pub struct MessageWindowsIter<I> {
1114    inner: I,
1115    buf: Vec<crate::OwnedSegment>,
1116    in_message: bool,
1117    /// Set to `true` after any terminal condition (error or clean EOF) so that
1118    /// subsequent `next()` calls immediately return `None`.
1119    done: bool,
1120}
1121
1122impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> MessageWindowsIter<I> {
1123    /// Wrap any owned-segment iterator as a message-window iterator.
1124    pub fn new(inner: I) -> Self {
1125        Self {
1126            inner,
1127            buf: Vec::new(),
1128            in_message: false,
1129            done: false,
1130        }
1131    }
1132}
1133
1134impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> Iterator
1135    for MessageWindowsIter<I>
1136{
1137    type Item = Result<OwnedMessageWindow, EdifactError>;
1138
1139    fn next(&mut self) -> Option<Self::Item> {
1140        if self.done {
1141            return None;
1142        }
1143        loop {
1144            let segment = match self.inner.next() {
1145                Some(Ok(s)) => s,
1146                Some(Err(e)) => {
1147                    self.done = true;
1148                    return Some(Err(e));
1149                }
1150                None => {
1151                    self.done = true;
1152                    // A window that opened (UNH seen) but never closed (no UNT)
1153                    // means the stream was truncated — surface as an error.
1154                    if self.in_message && !self.buf.is_empty() {
1155                        self.in_message = false;
1156                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
1157                        return Some(Err(EdifactError::UnexpectedEof { offset }));
1158                    }
1159                    return None;
1160                }
1161            };
1162
1163            match segment.tag.as_str() {
1164                "UNH" => {
1165                    if self.in_message {
1166                        // Malformed: new UNH without closing the prior UNT.
1167                        self.buf.clear();
1168                        self.in_message = false;
1169                        self.done = true;
1170                        return Some(Err(EdifactError::InvalidSegmentForMessage {
1171                            tag: "UNH".to_owned(),
1172                            message_type: "ENVELOPE".to_owned(),
1173                            span: segment.span,
1174                        }));
1175                    }
1176                    self.buf.clear();
1177                    self.in_message = true;
1178                    self.buf.push(segment);
1179                }
1180                "UNT" if self.in_message => {
1181                    self.buf.push(segment);
1182                    self.in_message = false;
1183                    let segments = std::mem::take(&mut self.buf);
1184                    return Some(Ok(OwnedMessageWindow::from_segments(segments)));
1185                }
1186                _ if self.in_message => {
1187                    self.buf.push(segment);
1188                }
1189                _ => {
1190                    // Envelope segment outside a window — skip.
1191                }
1192            }
1193        }
1194    }
1195}
1196
1197/// Stream-parse EDIFACT bytes into an iterator of per-message windows.
1198///
1199/// Each yielded [`MessageWindow`] spans one `UNH..UNT` pair, with segments
1200/// borrowing from `input` for their text content. Segment assembly is
1201/// zero-copy for borrowed input bytes; release-character unescaping may
1202/// allocate owned component strings when necessary.
1203/// Envelope segments (`UNB`, `UNZ`, …) are skipped automatically.
1204///
1205/// The `message_type` and `association_code` fields are populated directly from
1206/// the `UNH` segment so that routing logic does not need to traverse `segments`.
1207///
1208/// # Example
1209/// ```
1210/// use edifact_rs::from_bytes_windows;
1211/// let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'\
1212///               UNH+1+ORDERS:D:96A:UN'\
1213///               BGM+220+PO-001+9'\
1214///               UNT+3+1'\
1215///               UNZ+1+1'";
1216///
1217/// let windows: Vec<_> = from_bytes_windows(input)
1218///     .collect::<Result<_, _>>()
1219///     .unwrap();
1220/// assert_eq!(windows.len(), 1);
1221/// assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1222/// assert_eq!(windows[0].segments[0].tag, "UNH");
1223/// assert_eq!(windows[0].segments.last().unwrap().tag, "UNT");
1224/// ```
1225pub fn message_windows_bytes(input: &[u8]) -> MessageWindowsSliceIter<'_> {
1226    MessageWindowsSliceIter::new(crate::from_bytes(input))
1227}
1228
1229/// Stream-parse EDIFACT from a reader into an iterator of per-message windows.
1230///
1231/// Each yielded [`OwnedMessageWindow`] spans one `UNH..UNT` pair.
1232/// This variant reads lazily — only enough input to complete one window is
1233/// consumed per [`Iterator::next`] call.
1234pub fn message_windows_from_reader<R: Read>(
1235    reader: R,
1236) -> MessageWindowsIter<crate::FromReaderIter<R>> {
1237    MessageWindowsIter::new(crate::from_reader(reader))
1238}
1239
1240/// Stream typed messages from a reader by deserializing each `UNH..UNT` window.
1241///
1242/// This is the highest-level streaming API: it returns one `T` per message,
1243/// reading only as much data as needed to complete each window.
1244///
1245/// Each message window is deserialized via
1246/// [`EdifactDeserialize::edifact_deserialize_owned`], which avoids the
1247/// intermediate `Vec<Segment<'_>>` allocation incurred by the slice-based path.
1248/// Types derived with `#[derive(EdifactDeserialize)]` provide an efficient
1249/// override; manual implementations fall back to [`crate::OwnedSegment::as_borrowed`].
1250///
1251/// # Example
1252/// ```ignore
1253/// // Assuming `OrdersMessage` implements `EdifactDeserialize`:
1254/// let messages: Vec<OrdersMessage> =
1255///     deserialize_messages_from_reader::<OrdersMessage, _>(reader)
1256///         .collect::<Result<_, _>>()?;
1257/// ```
1258pub fn deserialize_messages_from_reader<T, R>(
1259    reader: R,
1260) -> impl Iterator<Item = Result<T, EdifactError>>
1261where
1262    T: EdifactDeserialize,
1263    R: Read,
1264{
1265    message_windows_from_reader(reader).map(|window| {
1266        let window = window?;
1267        T::edifact_deserialize_owned(&window.segments)
1268    })
1269}
1270
1271/// Stream typed messages from a byte slice by deserializing each `UNH..UNT` window.
1272pub fn deserialize_messages_bytes<T>(
1273    input: &[u8],
1274) -> impl Iterator<Item = Result<T, EdifactError>> + '_
1275where
1276    T: EdifactDeserialize,
1277{
1278    message_windows_bytes(input).map(|window| {
1279        let window = window?;
1280        T::edifact_deserialize(&window.segments)
1281    })
1282}
1283
1284// ── MessageDispatch ───────────────────────────────────────────────────────────
1285
1286/// A type-erased deserialized message produced by [`MessageDispatch`].
1287pub struct DispatchedMessage {
1288    /// The EDIFACT message type string extracted from the `UNH` segment.
1289    pub message_type: String,
1290    value: Box<dyn std::any::Any + Send + Sync>,
1291}
1292
1293impl DispatchedMessage {
1294    /// Attempt to downcast the inner value to `T`.
1295    ///
1296    /// Returns `None` if the stored type does not match `T`.
1297    pub fn downcast<T: std::any::Any + Send + Sync + 'static>(&self) -> Option<&T> {
1298        self.value.downcast_ref::<T>()
1299    }
1300}
1301
1302impl std::fmt::Debug for DispatchedMessage {
1303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1304        f.debug_struct("DispatchedMessage")
1305            .field("message_type", &self.message_type)
1306            .finish_non_exhaustive()
1307    }
1308}
1309
1310type DispatchHandlerFn = Box<
1311    dyn for<'a> Fn(&[Segment<'a>]) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1312        + Send
1313        + Sync,
1314>;
1315
1316type FallbackHandlerFn = Box<
1317    dyn for<'a> Fn(
1318            &[Segment<'a>],
1319            &str,
1320        ) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1321        + Send
1322        + Sync,
1323>;
1324
1325/// Type-based dispatcher for mixed-message EDIFACT streams.
1326///
1327/// Register one handler per message type with [`on`][Self::on], then call
1328/// [`dispatch`][Self::dispatch] on each message window.  If no handler matches
1329/// and a [`fallback`][Self::fallback] was registered it is invoked instead;
1330/// otherwise an [`EdifactError::UnexpectedMessageType`] is returned.
1331///
1332/// # Example
1333///
1334/// ```rust,ignore
1335/// let dispatch = MessageDispatch::new()
1336///     .on("ORDERS",  |segs| Orders::edifact_deserialize(segs))
1337///     .on("INVOIC",  |segs| Invoice::edifact_deserialize(segs));
1338///
1339/// for window in message_windows_bytes(input) {
1340///     let window = window?;
1341///     let msg = dispatch.dispatch(&window)?;
1342///     match msg.message_type.as_str() {
1343///         "ORDERS"  => { let o = msg.downcast::<Orders>().unwrap(); /* … */ }
1344///         "INVOIC"  => { let i = msg.downcast::<Invoice>().unwrap(); /* … */ }
1345///         _         => unreachable!(),
1346///     }
1347/// }
1348/// ```
1349pub struct MessageDispatch {
1350    handlers: Vec<(String, DispatchHandlerFn)>,
1351    fallback: Option<FallbackHandlerFn>,
1352}
1353
1354impl Default for MessageDispatch {
1355    fn default() -> Self {
1356        Self::new()
1357    }
1358}
1359
1360impl MessageDispatch {
1361    /// Create an empty dispatcher.
1362    pub fn new() -> Self {
1363        Self {
1364            handlers: Vec::new(),
1365            fallback: None,
1366        }
1367    }
1368
1369    /// Register a handler for `message_type`.
1370    ///
1371    /// The closure receives the full message window and returns a typed value
1372    /// that is boxed and stored inside [`DispatchedMessage`].
1373    pub fn on<T, F>(mut self, message_type: &str, handler: F) -> Self
1374    where
1375        T: std::any::Any + Send + Sync + 'static,
1376        F: for<'a> Fn(&[Segment<'a>]) -> Result<T, EdifactError> + Send + Sync + 'static,
1377    {
1378        let erased: DispatchHandlerFn = Box::new(move |segs| {
1379            let val = handler(segs)?;
1380            Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1381        });
1382        self.handlers.push((message_type.to_owned(), erased));
1383        self
1384    }
1385
1386    /// Register a fallback handler for unrecognised message types.
1387    ///
1388    /// The closure receives the segment window **and** the unknown message-type
1389    /// string.
1390    pub fn fallback<T, F>(mut self, handler: F) -> Self
1391    where
1392        T: std::any::Any + Send + Sync + 'static,
1393        F: for<'a> Fn(&[Segment<'a>], &str) -> Result<T, EdifactError> + Send + Sync + 'static,
1394    {
1395        let erased: FallbackHandlerFn = Box::new(move |segs, mt| {
1396            let val = handler(segs, mt)?;
1397            Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1398        });
1399        self.fallback = Some(erased);
1400        self
1401    }
1402
1403    /// Dispatch a single message window to the appropriate handler.
1404    ///
1405    /// The message type is extracted from the `UNH` segment.  If no `UNH` is
1406    /// present, [`EdifactError::MissingSegment`] is returned.
1407    pub fn dispatch(&self, window: &[Segment<'_>]) -> Result<DispatchedMessage, EdifactError> {
1408        let message_type = window
1409            .iter()
1410            .find(|s| s.tag == "UNH")
1411            .and_then(|unh| unh.get_element(1))
1412            .and_then(|e| e.get_component(0))
1413            .map(|s| s.to_owned())
1414            .ok_or_else(|| EdifactError::MissingSegment {
1415                tag: "UNH".to_owned(),
1416                expected_position: "first segment of message window".to_owned(),
1417            })?;
1418
1419        for (mt, handler) in &self.handlers {
1420            if *mt == message_type {
1421                let value = handler(window)?;
1422                return Ok(DispatchedMessage {
1423                    message_type,
1424                    value,
1425                });
1426            }
1427        }
1428
1429        if let Some(fallback) = &self.fallback {
1430            let value = fallback(window, &message_type)?;
1431            return Ok(DispatchedMessage {
1432                message_type,
1433                value,
1434            });
1435        }
1436
1437        Err(EdifactError::UnexpectedMessageType { message_type })
1438    }
1439
1440    /// Dispatch all messages from a byte reader.
1441    ///
1442    /// Each message window is extracted and dispatched in order.  The returned
1443    /// iterator is lazy — errors are yielded as `Err` items.
1444    pub fn dispatch_all_from_bytes<'a>(
1445        &'a self,
1446        input: &'a [u8],
1447    ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + 'a {
1448        message_windows_bytes(input).map(move |window| {
1449            let window = window?;
1450            self.dispatch(&window.segments)
1451        })
1452    }
1453
1454    /// Dispatch all messages from a reader.
1455    ///
1456    /// Parses the stream into message windows and dispatches each.  The
1457    /// returned iterator yields owned [`DispatchedMessage`] values lazily:
1458    /// each window is fully buffered in memory (as `Vec<OwnedSegment>`) before
1459    /// dispatch, but windows are processed one at a time rather than all at once.
1460    pub fn dispatch_all_from_reader<R: Read + 'static>(
1461        &self,
1462        reader: R,
1463    ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + '_ {
1464        message_windows_from_reader(reader).map(|window| {
1465            let window = window?;
1466            let borrowed: Vec<Segment<'_>> =
1467                window.segments.iter().map(|s| s.as_borrowed()).collect();
1468            self.dispatch(&borrowed)
1469        })
1470    }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    use super::*;
1476
1477    // ── manual test impl ──────────────────────────────────────────────────────
1478    #[derive(Debug, PartialEq)]
1479    struct BgmSegment {
1480        doc_name_code: String,
1481        pruef_id: String,
1482        msg_function: Option<String>,
1483    }
1484
1485    impl EdifactSegmentTag for BgmSegment {
1486        const SEGMENT_TAG: &'static str = "BGM";
1487    }
1488
1489    struct NadM;
1490
1491    impl EdifactSegmentTag for NadM {
1492        const SEGMENT_TAG: &'static str = "NAD";
1493        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1494    }
1495
1496    struct NadWildcard;
1497
1498    impl EdifactSegmentTag for NadWildcard {
1499        const SEGMENT_TAG: &'static str = "NAD";
1500        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1501    }
1502
1503    impl EdifactDeserialize for BgmSegment {
1504        fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
1505            let seg = find_segment(segments, "BGM").ok_or_else(|| {
1506                EdifactError::MissingRequiredElement {
1507                    tag: "BGM".to_owned(),
1508                    element_index: 0,
1509                }
1510            })?;
1511            Ok(Self {
1512                doc_name_code: element_str(seg, 0).to_owned(),
1513                pruef_id: element_str(seg, 1).to_owned(),
1514                msg_function: seg
1515                    .element_str(2)
1516                    .filter(|s| !s.is_empty())
1517                    .map(str::to_owned),
1518            })
1519        }
1520    }
1521
1522    #[test]
1523    fn deserialize_single_segment() {
1524        let input = b"BGM+E03+11042+9'";
1525        let bgm: BgmSegment = deserialize(input).unwrap();
1526        assert_eq!(bgm.doc_name_code, "E03");
1527        assert_eq!(bgm.pruef_id, "11042");
1528        assert_eq!(bgm.msg_function, Some("9".to_owned()));
1529    }
1530
1531    #[test]
1532    fn streaming_deserialize_first_from_bytes() {
1533        let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
1534        let bgm: BgmSegment = deserialize_first_streaming(input).unwrap();
1535        assert_eq!(bgm.pruef_id, "11042");
1536    }
1537
1538    #[test]
1539    fn streaming_deserialize_all_from_bytes() {
1540        let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
1541        let bgms: Vec<BgmSegment> = deserialize_all_streaming(input).unwrap();
1542        assert_eq!(bgms.len(), 2);
1543        assert_eq!(bgms[0].pruef_id, "11042");
1544        assert_eq!(bgms[1].pruef_id, "11043");
1545    }
1546
1547    #[test]
1548    fn streaming_deserialize_first_from_reader() {
1549        let input =
1550            std::io::Cursor::new(b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'".to_vec());
1551        let bgm: BgmSegment = deserialize_first_from_reader(input).unwrap();
1552        assert_eq!(bgm.pruef_id, "11042");
1553    }
1554
1555    #[test]
1556    fn streaming_deserialize_all_from_reader() {
1557        let input = std::io::Cursor::new(b"BGM+E03+11042+9'BGM+E01+11043+9'".to_vec());
1558        let bgms: Vec<BgmSegment> = deserialize_all_from_reader(input).unwrap();
1559        assert_eq!(bgms.len(), 2);
1560        assert_eq!(bgms[0].pruef_id, "11042");
1561        assert_eq!(bgms[1].pruef_id, "11043");
1562    }
1563
1564    #[test]
1565    fn missing_segment_returns_error() {
1566        let input = b"DTM+137:20230401:102'";
1567        let result: Result<BgmSegment, _> = deserialize(input);
1568        assert!(result.is_err());
1569    }
1570
1571    #[test]
1572    fn vec_collects_all_matching_segments() {
1573        let input = b"DTM+137:20230401:102'BGM+E03+11042+9'BGM+E01+11043+9'";
1574        let bgms: Vec<BgmSegment> = deserialize(input).unwrap();
1575        assert_eq!(bgms.len(), 2);
1576        assert_eq!(bgms[0].pruef_id, "11042");
1577        assert_eq!(bgms[1].pruef_id, "11043");
1578    }
1579
1580    #[test]
1581    fn find_qualified_segment_matches_qualifier() {
1582        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1583        let segments: Vec<Segment<'_>> =
1584            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1585        let nad_ms = find_qualified_segment(&segments, "NAD", "MS");
1586        let nad_mr = find_qualified_segment(&segments, "NAD", "MR");
1587        assert!(nad_ms.is_some());
1588        assert!(nad_mr.is_some());
1589        assert_eq!(element_str(nad_ms.unwrap(), 0), "MS");
1590        assert_eq!(element_str(nad_mr.unwrap(), 0), "MR");
1591    }
1592
1593    #[test]
1594    fn round_trip_str_api() {
1595        let input = "BGM+E03+11042+9'";
1596        let bgm: BgmSegment = deserialize_str(input).unwrap();
1597        assert_eq!(bgm.pruef_id, "11042");
1598    }
1599
1600    #[test]
1601    fn required_element_extraction() {
1602        let input = b"BGM+E03+11042+9'";
1603        let segments: Vec<Segment<'_>> =
1604            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1605        let seg = &segments[0];
1606
1607        assert_eq!(required_element(seg, 0).unwrap(), "E03");
1608        assert_eq!(required_element(seg, 1).unwrap(), "11042");
1609        // Element 5 doesn't exist
1610        assert!(required_element(seg, 5).is_err());
1611    }
1612
1613    #[test]
1614    fn optional_element_extraction() {
1615        let input = b"BGM+E03+11042+9'BGM+E01++absent'";
1616        let segments: Vec<Segment<'_>> =
1617            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1618
1619        // First segment
1620        assert_eq!(optional_element(&segments[0], 0), Some("E03"));
1621        assert_eq!(optional_element(&segments[0], 1), Some("11042"));
1622        assert_eq!(optional_element(&segments[0], 5), None);
1623
1624        // Second segment with empty element
1625        assert_eq!(optional_element(&segments[1], 1), None);
1626    }
1627
1628    #[test]
1629    fn component_extraction() {
1630        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1631        let segments: Vec<Segment<'_>> =
1632            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1633        let seg = &segments[0];
1634
1635        assert_eq!(required_component(seg, 0, 0).unwrap(), "UNOA");
1636        assert_eq!(required_component(seg, 0, 1).unwrap(), "1");
1637        // Non-existent component
1638        assert!(required_component(seg, 0, 5).is_err());
1639    }
1640
1641    #[test]
1642    fn composite_element_helper() {
1643        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1644        let segments: Vec<Segment<'_>> =
1645            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1646        let seg = &segments[0];
1647
1648        let comp = composite_element(seg, 0).unwrap();
1649        assert_eq!(comp.len(), 2);
1650        assert_eq!(comp.get(0), Some("UNOA"));
1651        assert_eq!(comp.get(1), Some("1"));
1652        assert_eq!(comp.get(5), None);
1653        assert_eq!(comp.get_or_empty(5), "");
1654    }
1655
1656    #[test]
1657    fn get_all_components() {
1658        // UNB has composite element: UNOA:1
1659        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1660        let segments: Vec<Segment<'_>> =
1661            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1662        let seg = &segments[0];
1663
1664        let comps: Vec<&str> = get_components_iter(seg, 0).collect(); // First element is UNOA:1
1665        assert!(!comps.is_empty(), "Expected components but got empty");
1666        assert_eq!(comps.len(), 2);
1667        assert_eq!(comps[0], "UNOA");
1668        assert_eq!(comps[1], "1");
1669    }
1670
1671    #[test]
1672    fn qualifier_pattern_matching_supports_exact_and_wildcard() {
1673        // Exact match (no wildcard)
1674        assert!(qualifier_matches_pattern("MS", "MS"));
1675        assert!(!qualifier_matches_pattern("MS", "M")); // Not a prefix match after R-003
1676        // Wildcard patterns
1677        assert!(qualifier_matches_pattern("MS", "M*"));
1678        assert!(qualifier_matches_pattern("MRY", "M*Y"));
1679        assert!(!qualifier_matches_pattern("AB", "M*"));
1680    }
1681
1682    /// Comprehensive table-driven tests for `qualifier_matches_pattern`.
1683    #[test]
1684    fn qualifier_matches_pattern_table() {
1685        // (value, pattern, expected)
1686        let cases: &[(&str, &str, bool)] = &[
1687            // ── empty inputs ────────────────────────────────────────────────
1688            ("", "", true),   // empty matches empty
1689            ("", "*", true),  // wildcard matches empty string
1690            ("A", "", false), // non-empty does not match empty pattern
1691            ("", "A", false), // empty does not match non-empty literal
1692            // ── literal (no wildcard) ────────────────────────────────────────
1693            ("MS", "MS", true),
1694            ("BY", "BY", true),
1695            ("ms", "MS", false),  // case-sensitive
1696            ("MSX", "MS", false), // prefix is NOT a match without wildcard
1697            ("M", "MS", false),   // too short
1698            // ── single wildcard at the end (prefix match) ────────────────────
1699            ("MS", "M*", true),
1700            ("MULTI", "MUL*", true),
1701            ("AB", "M*", false),
1702            ("", "M*", false), // empty does not start with 'M'
1703            // ── single wildcard at the start (suffix match) ──────────────────
1704            ("MSG", "*G", true),
1705            ("G", "*G", true),
1706            ("MSG", "*X", false),
1707            ("", "*G", false),
1708            // ── wildcard in the middle ───────────────────────────────────────
1709            ("MRY", "M*Y", true),
1710            ("MAY", "M*Y", true),
1711            ("MY", "M*Y", true),    // zero-width wildcard: "M" + "" + "Y"
1712            ("MYY", "M*Y", true),   // last 'Y' matches, wildcard = 'Y'
1713            ("MAYZ", "M*Y", false), // does not end with 'Y'
1714            ("AB", "M*Y", false),
1715            // ── bare wildcard (match-all) ────────────────────────────────────
1716            ("*", "*", true), // literal '*' value vs wildcard pattern
1717            ("anything", "*", true),
1718            ("", "*", true),
1719            // ── multiple wildcards ────────────────────────────────────────────
1720            ("ABCDE", "A*C*E", true),
1721            ("ACE", "A*C*E", true), // zero-width wildcards
1722            ("AXCYE", "A*C*E", true),
1723            ("ABCDF", "A*C*E", false),
1724            // ── wildcard with empty segment between stars ─────────────────────
1725            ("AB", "A**B", true), // "A**B" → parts ["A", "", "B"] → ends_with_wildcard?
1726            // ── pattern longer than value ─────────────────────────────────────
1727            ("AB", "A*B*C", false),
1728            // ── value contains pattern as substring but must anchor start ─────
1729            ("XMS", "MS", false),
1730        ];
1731
1732        for (value, pattern, expected) in cases {
1733            let got = qualifier_matches_pattern(value, pattern);
1734            assert_eq!(
1735                got, *expected,
1736                "qualifier_matches_pattern({value:?}, {pattern:?}) expected {expected} but got {got}"
1737            );
1738        }
1739    }
1740
1741    #[test]
1742    fn typed_qualifier_helpers_work() {
1743        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1744        let segments: Vec<Segment<'_>> =
1745            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1746
1747        let first = find_segment_typed::<NadM>(&segments).unwrap();
1748        assert_eq!(first.element_str(0), Some("MS"));
1749
1750        let all: Vec<_> = find_segments_typed::<NadWildcard>(&segments).collect();
1751        assert_eq!(all.len(), 2);
1752    }
1753
1754    #[test]
1755    fn segment_accessor_trait_methods_work() {
1756        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1757        let segments: Vec<Segment<'_>> =
1758            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1759        let seg = &segments[0];
1760
1761        assert_eq!(SegmentAccessor::get_element(seg, 1), Some("SENDER"));
1762        assert_eq!(SegmentAccessor::required_composite(seg, 0, 1).unwrap(), "1");
1763        let parsed: i32 = SegmentAccessor::code_element(seg, 4).unwrap();
1764        assert_eq!(parsed, 1);
1765        let reps = SegmentAccessor::component_range(seg, 3, 0, 2).unwrap();
1766        assert_eq!(reps, vec!["200101", "0900"]);
1767    }
1768
1769    #[test]
1770    fn group_helpers_detect_contiguity() {
1771        struct NadAny;
1772        impl EdifactSegmentTag for NadAny {
1773            const SEGMENT_TAG: &'static str = "NAD";
1774        }
1775
1776        let contiguous_input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'";
1777        let contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(contiguous_input)
1778            .collect::<Result<_, _>>()
1779            .unwrap();
1780        assert!(groups_are_contiguous_by_qualifier::<NadAny>(
1781            &contiguous_segments
1782        ));
1783
1784        let non_contiguous_input = b"NAD+MS+1'RFF+AA:1'NAD+MR+2'";
1785        let non_contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(non_contiguous_input)
1786            .collect::<Result<_, _>>()
1787            .unwrap();
1788        assert!(!groups_are_contiguous_by_qualifier::<NadAny>(
1789            &non_contiguous_segments
1790        ));
1791    }
1792
1793    #[test]
1794    fn group_helpers_collect_contiguous_groups() {
1795        struct NadAny;
1796        impl EdifactSegmentTag for NadAny {
1797            const SEGMENT_TAG: &'static str = "NAD";
1798        }
1799
1800        let input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'NAD+BY+3'";
1801        let segments: Vec<Segment<'_>> =
1802            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1803        let groups = contiguous_groups_by_qualifier::<NadAny>(&segments);
1804
1805        assert_eq!(groups.len(), 2);
1806        assert_eq!(groups[0].len(), 2);
1807        assert_eq!(groups[1].len(), 1);
1808    }
1809
1810    // ── MessageWindowsIter tests ──────────────────────────────────────────────
1811
1812    #[test]
1813    fn message_windows_bytes_yields_complete_windows() {
1814        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1815                      UNH+1+ORDERS:D:96A:UN'\
1816                      BGM+220+PO-001+9'\
1817                      UNT+3+1'\
1818                      UNZ+1+1'";
1819        let windows: Vec<_> = message_windows_bytes(input)
1820            .collect::<Result<_, _>>()
1821            .unwrap();
1822        assert_eq!(windows.len(), 1);
1823        assert_eq!(windows[0].segments[0].tag, "UNH");
1824        assert_eq!(windows[0].segments.last().unwrap().tag, "UNT");
1825        assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1826        assert_eq!(windows[0].association_code.as_deref(), None);
1827    }
1828
1829    #[test]
1830    fn message_windows_bytes_preserves_owned_unh_metadata() {
1831        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1832                      UNH+1+ORD?ERS:D:96A:UN:5??5??3a'\
1833                      BGM+220+PO-001+9'\
1834                      UNT+3+1'\
1835                      UNZ+1+1'";
1836        let windows: Vec<_> = message_windows_bytes(input)
1837            .collect::<Result<_, _>>()
1838            .unwrap();
1839
1840        assert_eq!(windows.len(), 1);
1841        assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1842        assert_eq!(windows[0].association_code.as_deref(), Some("5?5?3a"));
1843    }
1844
1845    #[test]
1846    fn message_windows_truncated_stream_returns_error() {
1847        // Stream ends after UNH and BGM but without UNT — truncation must be an error
1848        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1849        let results: Vec<_> = message_windows_bytes(input).collect();
1850        assert_eq!(results.len(), 1);
1851        assert!(
1852            matches!(results[0], Err(EdifactError::UnexpectedEof { .. })),
1853            "expected UnexpectedEof for truncated window, got: {:?}",
1854            results[0]
1855        );
1856    }
1857
1858    #[test]
1859    fn message_windows_subsequent_calls_return_none_after_truncation() {
1860        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1861        let mut iter = message_windows_bytes(input);
1862        assert!(matches!(
1863            iter.next(),
1864            Some(Err(EdifactError::UnexpectedEof { .. }))
1865        ));
1866        // After the error, the iterator must be fused (done = true)
1867        assert!(iter.next().is_none());
1868    }
1869
1870    #[test]
1871    fn message_windows_unh_without_unt_before_next_unh_returns_error() {
1872        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'\
1873                      UNH+2+ORDERS:D:96A:UN'BGM+220+PO-002+9'UNT+3+2'";
1874        let results: Vec<_> = message_windows_bytes(input).collect();
1875        // First item must be an error (UNH before UNT — missing closer)
1876        assert!(
1877            matches!(
1878                results[0],
1879                Err(EdifactError::InvalidSegmentForMessage { ref tag, .. }) if tag == "UNH"
1880            ),
1881            "expected InvalidSegmentForMessage(UNH), got: {:?}",
1882            results[0]
1883        );
1884    }
1885
1886    // ── SegmentAccessor unit tests ─────────────────────────────────────────────
1887
1888    fn parse_one(input: &str) -> crate::OwnedSegment {
1889        crate::from_reader_collect(std::io::Cursor::new(input.as_bytes()))
1890            .expect("parse failed")
1891            .into_iter()
1892            .next()
1893            .expect("at least one segment")
1894    }
1895
1896    #[test]
1897    fn segment_accessor_get_element_returns_value() {
1898        let owned = parse_one("BGM+220+PO-001+9'");
1899        let seg = owned.as_borrowed();
1900        assert_eq!(SegmentAccessor::get_element(&seg, 0), Some("220"));
1901        assert_eq!(SegmentAccessor::get_element(&seg, 1), Some("PO-001"));
1902        assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("9"));
1903        assert_eq!(
1904            SegmentAccessor::get_element(&seg, 9),
1905            None,
1906            "out-of-bounds must return None"
1907        );
1908    }
1909
1910    #[test]
1911    fn segment_accessor_get_element_filters_empty() {
1912        let owned = parse_one("TST+++VALUE'");
1913        let seg = owned.as_borrowed();
1914        // elements 0 and 1 are empty; element 2 is "VALUE"
1915        assert_eq!(
1916            SegmentAccessor::get_element(&seg, 0),
1917            None,
1918            "empty element must return None"
1919        );
1920        assert_eq!(
1921            SegmentAccessor::get_element(&seg, 1),
1922            None,
1923            "empty element must return None"
1924        );
1925        assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("VALUE"));
1926    }
1927
1928    #[test]
1929    fn segment_accessor_get_component_returns_value() {
1930        let owned = parse_one("UNH+1+ORDERS:D:96A:UN'");
1931        let seg = owned.as_borrowed();
1932        assert_eq!(seg.get_component(1, 0), Some("ORDERS"));
1933        assert_eq!(seg.get_component(1, 1), Some("D"));
1934        assert_eq!(seg.get_component(1, 2), Some("96A"));
1935        assert_eq!(seg.get_component(1, 3), Some("UN"));
1936        assert_eq!(
1937            seg.get_component(1, 9),
1938            None,
1939            "out-of-bounds must return None"
1940        );
1941    }
1942
1943    #[test]
1944    fn segment_accessor_text_element_errors_on_missing() {
1945        let owned = parse_one("BGM+'");
1946        let seg = owned.as_borrowed();
1947        // element 0 is empty — text_element must return an error
1948        let err = seg.text_element(0);
1949        assert!(
1950            matches!(err, Err(EdifactError::MissingRequiredElement { ref tag, element_index: 0 }) if tag == "BGM"),
1951            "expected MissingRequiredElement, got: {err:?}"
1952        );
1953    }
1954
1955    #[test]
1956    fn segment_accessor_required_composite_errors_on_missing() {
1957        let owned = parse_one("DTM+137'");
1958        let seg = owned.as_borrowed();
1959        // component 1 of element 0 is absent
1960        let err = seg.required_composite(0, 1);
1961        assert!(
1962            matches!(err, Err(EdifactError::MissingRequiredComponent { ref tag, element_index: 0, component_index: 1 }) if tag == "DTM"),
1963            "expected MissingRequiredComponent, got: {err:?}"
1964        );
1965    }
1966
1967    #[test]
1968    fn segment_accessor_code_element_parses_integer() {
1969        let owned = parse_one("QTY+21:100'");
1970        let seg = owned.as_borrowed();
1971        let qty: u32 = seg.code_element(0).expect("should parse qualifier as u32");
1972        assert_eq!(qty, 21);
1973    }
1974
1975    #[test]
1976    fn segment_accessor_optional_element_absent_returns_none() {
1977        let owned = parse_one("BGM+220'");
1978        let seg = owned.as_borrowed();
1979        assert_eq!(seg.optional_element(5), None);
1980    }
1981}