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/// A composite data element wrapper for clearer ergonomics.
543///
544/// Holds borrowed `&'a str` references to the underlying data — no string
545/// copies are made.  Up to 4 component pointers are stored inline (via
546/// [`SmallVec`]) so the common case is fully allocation-free.
547///
548/// The lifetime `'a` represents the underlying data lifetime.
549///
550/// [`SmallVec`]: smallvec::SmallVec
551pub struct CompositeElement<'a> {
552    components: smallvec::SmallVec<[&'a str; 4]>,
553}
554
555impl<'a> CompositeElement<'a> {
556    /// Create a `CompositeElement` from a pre-existing `Cow` component slice.
557    ///
558    /// Used internally by generated owned-deserialization code.
559    pub fn from_slice(components: &'a [std::borrow::Cow<'a, str>]) -> Self {
560        Self {
561            components: components.iter().map(|c| c.as_ref()).collect(),
562        }
563    }
564
565    /// Crate-private constructor for direct `&str` components.
566    pub(crate) fn from_strs(components: smallvec::SmallVec<[&'a str; 4]>) -> Self {
567        Self { components }
568    }
569
570    /// Get the component at index `i`, or None if absent.
571    pub fn get(&self, i: usize) -> Option<&'a str> {
572        self.components.get(i).copied()
573    }
574
575    /// Get the component at index `i`, or empty string if absent.
576    pub fn get_or_empty(&self, i: usize) -> &'a str {
577        self.get(i).unwrap_or("")
578    }
579
580    /// Get the number of components.
581    pub fn len(&self) -> usize {
582        self.components.len()
583    }
584
585    /// Check if the composite is empty.
586    pub fn is_empty(&self) -> bool {
587        self.components.is_empty()
588    }
589
590    /// Iterate over all component string values.
591    pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
592        self.components.iter().copied()
593    }
594}
595
596/// Get a composite element from a segment with clearer ergonomics.
597pub fn composite_element<'a, 'd: 'a>(
598    seg: &'a Segment<'d>,
599    idx: usize,
600) -> Option<CompositeElement<'a>> {
601    // `.collect()` into `SmallVec<[&str; 4]>` keeps ≤4-component elements
602    // fully on the stack (no heap allocation for the common case).
603    seg.elements.get(idx).map(|elem| {
604        CompositeElement::from_strs(elem.components.iter().map(|(c, _)| c.as_ref()).collect())
605    })
606}
607
608/// Find the first [`OwnedSegment`] with the given tag.
609///
610/// Zero-allocation counterpart of [`find_segment`] for use in
611/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
612///
613/// [`OwnedSegment`]: crate::OwnedSegment
614pub fn find_segment_owned<'s>(
615    segments: &'s [crate::OwnedSegment],
616    tag: &str,
617) -> Option<&'s crate::OwnedSegment> {
618    segments.iter().find(|s| s.tag == tag)
619}
620
621/// Find the first [`OwnedSegment`] with the given tag **and** qualifier.
622///
623/// The qualifier is compared against the first component of element 0.
624/// Zero-allocation counterpart of [`find_qualified_segment`] for use in
625/// [`EdifactDeserialize::edifact_deserialize_owned`] implementations.
626///
627/// [`OwnedSegment`]: crate::OwnedSegment
628pub fn find_qualified_segment_owned<'s>(
629    segments: &'s [crate::OwnedSegment],
630    tag: &str,
631    qualifier: &str,
632) -> Option<&'s crate::OwnedSegment> {
633    segments
634        .iter()
635        .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
636}
637
638/// Segment accessor trait for ergonomic typed extraction.
639pub trait SegmentAccessor<'a> {
640    /// Get non-empty element text at index `idx`.
641    fn get_element(&'a self, idx: usize) -> Option<&'a str>;
642    /// Get non-empty component text at element/component indexes.
643    fn get_component(&'a self, elem: usize, comp: usize) -> Option<&'a str>;
644    /// Get a composite wrapper for element `idx`.
645    fn get_composite(&'a self, idx: usize) -> Option<CompositeElement<'a>>;
646
647    /// Get required non-empty element text.
648    fn text_element(&'a self, idx: usize) -> Result<&'a str, EdifactError>;
649    /// Get optional non-empty element text.
650    fn optional_element(&'a self, idx: usize) -> Option<&'a str>;
651    /// Parse a typed code value from a required element.
652    fn code_element<T: FromStr>(&'a self, idx: usize) -> Result<T, EdifactError>;
653    /// Get required non-empty composite component.
654    fn required_composite(&'a self, elem: usize, comp: usize) -> Result<&'a str, EdifactError>;
655    /// Get `count` required components starting at `start_idx` from element `elem`.
656    ///
657    /// This walks *components inside one data element* — the `:`-separated parts
658    /// of a composite. It has nothing to do with ISO 9735-4 repeating data
659    /// elements; for those, read
660    /// [`Element::repetitions`][crate::Element::repetitions].
661    ///
662    /// Allocates a `Vec`.  For a zero-alloc alternative, use
663    /// [`component_range_iter`][Self::component_range_iter] and
664    /// consume the iterator directly without collecting.
665    fn component_range(
666        &'a self,
667        elem: usize,
668        start_idx: usize,
669        count: usize,
670    ) -> Result<Vec<&'a str>, EdifactError> {
671        // Default implementation delegates to the zero-alloc iterator and
672        // collects.  Implementors that can do better should override this.
673        self.component_range_iter(elem, start_idx, count).collect()
674    }
675
676    /// Iterate over `count` required components starting at `start_idx` from element `elem`.
677    ///
678    /// Allocation-free alternative to [`component_range`][Self::component_range];
679    /// the caller supplies the iteration budget and consumes results on the fly.
680    fn component_range_iter(
681        &'a self,
682        elem: usize,
683        start_idx: usize,
684        count: usize,
685    ) -> impl Iterator<Item = Result<&'a str, EdifactError>> + 'a;
686}
687
688impl<'s, 'd> SegmentAccessor<'s> for Segment<'d>
689where
690    'd: 's,
691{
692    fn get_element(&'s self, idx: usize) -> Option<&'s str> {
693        self.element_str(idx).filter(|s| !s.is_empty())
694    }
695
696    fn get_component(&'s self, elem: usize, comp: usize) -> Option<&'s str> {
697        self.elements
698            .get(elem)
699            .and_then(|e| e.get_component(comp))
700            .filter(|s| !s.is_empty())
701    }
702
703    fn get_composite(&'s self, idx: usize) -> Option<CompositeElement<'s>> {
704        composite_element(self, idx)
705    }
706
707    fn text_element(&'s self, idx: usize) -> Result<&'s str, EdifactError> {
708        <Self as SegmentAccessor>::get_element(self, idx).ok_or_else(|| {
709            EdifactError::MissingRequiredElement {
710                tag: self.tag.to_owned(),
711                element_index: idx,
712            }
713        })
714    }
715
716    fn optional_element(&'s self, idx: usize) -> Option<&'s str> {
717        <Self as SegmentAccessor>::get_element(self, idx)
718    }
719
720    fn code_element<T: FromStr>(&'s self, idx: usize) -> Result<T, EdifactError> {
721        let raw = self.text_element(idx)?;
722        raw.parse::<T>().map_err(|_| EdifactError::InvalidText {
723            offset: self
724                .element_span(idx)
725                .map(|s| s.start)
726                .unwrap_or(self.span.start),
727        })
728    }
729
730    fn required_composite(&'s self, elem: usize, comp: usize) -> Result<&'s str, EdifactError> {
731        match self.elements.get(elem) {
732            None => Err(EdifactError::MissingRequiredElement {
733                tag: self.tag.to_owned(),
734                element_index: elem,
735            }),
736            Some(e) => e
737                .get_component(comp)
738                .filter(|s| !s.is_empty())
739                .ok_or_else(|| EdifactError::MissingRequiredComponent {
740                    tag: self.tag.to_owned(),
741                    element_index: elem,
742                    component_index: comp,
743                }),
744        }
745    }
746
747    fn component_range_iter(
748        &'s self,
749        elem: usize,
750        start_idx: usize,
751        count: usize,
752    ) -> impl Iterator<Item = Result<&'s str, EdifactError>> + 's {
753        let tag = self.tag;
754        let element_exists = self.elements.get(elem).is_some();
755        let components = self
756            .elements
757            .get(elem)
758            .map(|e| e.components.as_slice())
759            .unwrap_or(&[]);
760        (start_idx..start_idx + count).map(move |idx| {
761            components
762                .get(idx)
763                .map(|(c, _)| c.as_ref())
764                .filter(|s| !s.is_empty())
765                .ok_or_else(|| {
766                    if element_exists {
767                        EdifactError::MissingRequiredComponent {
768                            tag: tag.to_owned(),
769                            element_index: elem,
770                            component_index: idx,
771                        }
772                    } else {
773                        EdifactError::MissingRequiredElement {
774                            tag: tag.to_owned(),
775                            element_index: elem,
776                        }
777                    }
778                })
779        })
780    }
781}
782
783// ── message-window streaming ──────────────────────────────────────────────────
784
785/// A complete `UNH..UNT` message window that borrows from the original input.
786///
787/// Produced by [`MessageWindowsSliceIter`] / [`message_windows_bytes`].
788/// The `message_type` and `association_code` fields are extracted from the
789/// `UNH` segment at construction time, so callers do not need to traverse the
790/// segment list themselves.
791///
792/// `segments` contains the full window including the `UNH` and `UNT` service
793/// segments so that envelope-aware consumers have access to them.
794///
795/// # Accessing segments
796///
797/// ```rust,ignore
798/// for window in message_windows_bytes(input) {
799///     let window = window?;
800///     println!("type={:?} code={:?}", window.message_type, window.association_code);
801///     let bgm = window.segments.iter().find(|s| s.tag == "BGM");
802/// }
803/// ```
804#[derive(Debug)]
805pub struct MessageWindow<'a> {
806    /// EDIFACT message type extracted from `UNH` element 1, component 0.
807    ///
808    /// Borrowed when the component can be referenced directly, owned when
809    /// release-character unescaping requires allocation.
810    pub message_type: Option<Cow<'a, str>>,
811    /// Association-assigned code (DE 0057) from `UNH` element 1, component 4.
812    ///
813    /// Borrowed when the component can be referenced directly, owned when
814    /// release-character unescaping requires allocation.
815    pub association_code: Option<Cow<'a, str>>,
816    /// All segments in this window, from `UNH` through `UNT` (inclusive).
817    pub segments: Vec<crate::Segment<'a>>,
818}
819
820impl<'a> MessageWindow<'a> {
821    /// The message **body**: everything between `UNH` and `UNT`, exclusive.
822    ///
823    /// [`segments`][Self::segments] deliberately includes the service segments so
824    /// that envelope-aware consumers can read them, but they are exactly what a
825    /// body-oriented pass does not want.  In particular
826    /// [`group_segments_indexed`][crate::group_segments_indexed] is driven by
827    /// trigger tags alone, so a trailing `UNT` lands inside whichever group ran
828    /// last — pass `body()` and it cannot.
829    ///
830    /// Missing service segments are tolerated: a window that somehow lacks its
831    /// `UNH` or `UNT` yields whatever it does have, rather than panicking.
832    ///
833    /// # Example
834    ///
835    /// ```
836    /// use edifact_rs::from_bytes_windows;
837    ///
838    /// let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'UNT+3+1'";
839    /// let windows: Vec<_> = from_bytes_windows(input).collect::<Result<Vec<_>, _>>()?;
840    ///
841    /// assert_eq!(windows[0].segments.len(), 3);      // UNH, BGM, UNT
842    /// assert_eq!(
843    ///     windows[0].body().iter().map(|s| s.tag).collect::<Vec<_>>(),
844    ///     ["BGM"],
845    /// );
846    /// # Ok::<(), edifact_rs::EdifactError>(())
847    /// ```
848    #[must_use]
849    pub fn body(&self) -> &[crate::Segment<'a>] {
850        body_of(&self.segments, |segment| segment.tag)
851    }
852
853    /// Build a `MessageWindow` from a completed segment buffer.
854    ///
855    /// Extracts `message_type` and `association_code` from the leading `UNH`
856    /// segment.  Metadata extraction is allocation-free for borrowed components;
857    /// release-character unescaping may allocate owned strings when necessary.
858    fn from_segments(segments: Vec<crate::Segment<'a>>) -> Self {
859        let message_type = segments
860            .first()
861            .filter(|s| s.tag == "UNH")
862            .and_then(|unh| unh_component(unh, 0));
863        let association_code = segments
864            .first()
865            .filter(|s| s.tag == "UNH")
866            .and_then(|unh| unh_component(unh, 4));
867        Self {
868            message_type,
869            association_code,
870            segments,
871        }
872    }
873}
874
875/// Extract a non-empty string component from UNH element 1, preserving the
876/// component's borrowed/owned state.
877///
878/// By using two distinct lifetime parameters (`'b` for the borrow of `seg`,
879/// `'a` for the segment data), we tell the borrow checker that the returned
880/// `&'a str` lives independently of how long we hold `&seg`, which lets callers
881/// move `seg` into a containing struct after this call returns.
882fn unh_component<'a, 'b>(seg: &'b crate::Segment<'a>, comp_idx: usize) -> Option<Cow<'a, str>>
883where
884    'a: 'b,
885{
886    seg.elements
887        .get(1)
888        .and_then(|e| e.components.get(comp_idx))
889        .and_then(|(c, _)| if c.is_empty() { None } else { Some(c.clone()) })
890}
891
892/// Strip a leading `UNH` and a trailing `UNT` from a window's segments.
893///
894/// Shared by the borrowed and owned windows so the two cannot disagree about
895/// what "the body" means.
896fn body_of<S>(segments: &[S], tag: impl Fn(&S) -> &str) -> &[S] {
897    let start = usize::from(segments.first().is_some_and(|s| tag(s) == "UNH"));
898    let end = segments.len().saturating_sub(usize::from(
899        segments.last().is_some_and(|s| tag(s) == "UNT"),
900    ));
901    segments.get(start..end).unwrap_or(&[])
902}
903
904/// An owned, heap-allocated `UNH..UNT` message window.
905///
906/// Produced by [`MessageWindowsIter`] / [`message_windows_from_reader`].
907/// Equivalent to [`MessageWindow`] but with all data owned, so it outlives
908/// the original reader.
909///
910/// `segments` contains the full window including the `UNH` and `UNT` service
911/// segments.
912#[derive(Debug, Clone)]
913pub struct OwnedMessageWindow {
914    /// EDIFACT message type extracted from `UNH` element 1, component 0.
915    pub message_type: Option<String>,
916    /// Association-assigned code (DE 0057) from `UNH` element 1, component 4.
917    pub association_code: Option<String>,
918    /// All segments in this window, from `UNH` through `UNT` (inclusive).
919    pub segments: Vec<crate::OwnedSegment>,
920}
921
922impl OwnedMessageWindow {
923    /// The message body: everything between `UNH` and `UNT`, exclusive.
924    ///
925    /// The owned counterpart of [`MessageWindow::body`].
926    #[must_use]
927    pub fn body(&self) -> &[crate::OwnedSegment] {
928        body_of(&self.segments, |segment| segment.tag.as_str())
929    }
930
931    fn from_segments(segments: Vec<crate::OwnedSegment>) -> Self {
932        let unh = segments.first().filter(|s| s.tag == "UNH");
933        let message_type = unh
934            .and_then(|s| s.elements.get(1))
935            .and_then(|e| e.components.first())
936            .map(|(c, _)| c.as_str())
937            .filter(|s| !s.is_empty())
938            .map(str::to_owned);
939        let association_code = unh
940            .and_then(|s| s.elements.get(1))
941            .and_then(|e| e.components.get(4))
942            .map(|(c, _)| c.as_str())
943            .filter(|s| !s.is_empty())
944            .map(str::to_owned);
945        Self {
946            message_type,
947            association_code,
948            segments,
949        }
950    }
951}
952
953/// An iterator that groups borrowed EDIFACT segments into per-message windows.
954///
955/// Zero-copy counterpart to [`MessageWindowsIter`] for in-memory byte slices.
956/// Text content borrows from the original input; segment structure allocates
957/// element vectors during parsing. Release-character unescaping may further
958/// allocate owned strings when escape sequences are present. Envelope segments
959/// outside a `UNH..UNT` pair are silently skipped.
960///
961/// Obtain this via [`message_windows_bytes`].
962pub struct MessageWindowsSliceIter<'a> {
963    inner: crate::FromBytesIter<'a>,
964    buf: Vec<crate::Segment<'a>>,
965    in_message: bool,
966    done: bool,
967}
968
969impl<'a> MessageWindowsSliceIter<'a> {
970    fn new(inner: crate::FromBytesIter<'a>) -> Self {
971        Self {
972            inner,
973            buf: Vec::new(),
974            in_message: false,
975            done: false,
976        }
977    }
978}
979
980impl<'a> Iterator for MessageWindowsSliceIter<'a> {
981    type Item = Result<MessageWindow<'a>, EdifactError>;
982
983    fn next(&mut self) -> Option<Self::Item> {
984        if self.done {
985            return None;
986        }
987        loop {
988            let segment = match self.inner.next() {
989                Some(Ok(s)) => s,
990                Some(Err(e)) => {
991                    self.done = true;
992                    return Some(Err(e));
993                }
994                None => {
995                    self.done = true;
996                    if self.in_message && !self.buf.is_empty() {
997                        self.in_message = false;
998                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
999                        return Some(Err(EdifactError::UnexpectedEof { offset }));
1000                    }
1001                    return None;
1002                }
1003            };
1004
1005            match segment.tag {
1006                "UNH" => {
1007                    if self.in_message {
1008                        self.buf.clear();
1009                        self.in_message = false;
1010                        self.done = true;
1011                        return Some(Err(EdifactError::InvalidSegmentForMessage {
1012                            tag: "UNH".to_owned(),
1013                            message_type: "ENVELOPE".to_owned(),
1014                            span: segment.span,
1015                        }));
1016                    }
1017                    self.buf.clear();
1018                    self.in_message = true;
1019                    self.buf.push(segment);
1020                }
1021                "UNT" if self.in_message => {
1022                    self.buf.push(segment);
1023                    self.in_message = false;
1024                    let segments = std::mem::take(&mut self.buf);
1025                    return Some(Ok(MessageWindow::from_segments(segments)));
1026                }
1027                _ if self.in_message => {
1028                    self.buf.push(segment);
1029                }
1030                _ => {
1031                    // Envelope segment outside a window — skip.
1032                }
1033            }
1034        }
1035    }
1036}
1037
1038/// An iterator that groups owned EDIFACT segments into per-message windows.
1039///
1040/// Each yielded item is an [`OwnedMessageWindow`] containing the segments for one
1041/// complete `UNH..UNT` message, inclusive of both service segments.
1042/// Envelope-level segments (`UNB`, `UNG`, `UNZ`, `UNE`) that sit outside any
1043/// `UNH..UNT` pair are silently skipped.
1044///
1045/// # Errors
1046///
1047/// - An inner-iterator error is forwarded immediately and iteration stops.
1048/// - A `UNH` seen while a prior window is still open (missing `UNT`) is an error.
1049/// - Input that ends while a `UNH` window is open (stream truncation) yields
1050///   `Err(EdifactError::UnexpectedEof { … })` before returning `None`.
1051///
1052/// # Construction
1053///
1054/// Use [`message_windows_from_reader`] or [`message_windows_bytes`] to
1055/// obtain a `MessageWindowsIter` directly.  For fully custom sources, call
1056/// [`MessageWindowsIter::new`] with any `Iterator<Item = Result<OwnedSegment,
1057/// EdifactError>>`.
1058pub struct MessageWindowsIter<I> {
1059    inner: I,
1060    buf: Vec<crate::OwnedSegment>,
1061    in_message: bool,
1062    /// Set to `true` after any terminal condition (error or clean EOF) so that
1063    /// subsequent `next()` calls immediately return `None`.
1064    done: bool,
1065}
1066
1067impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> MessageWindowsIter<I> {
1068    /// Wrap any owned-segment iterator as a message-window iterator.
1069    pub fn new(inner: I) -> Self {
1070        Self {
1071            inner,
1072            buf: Vec::new(),
1073            in_message: false,
1074            done: false,
1075        }
1076    }
1077}
1078
1079impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> Iterator
1080    for MessageWindowsIter<I>
1081{
1082    type Item = Result<OwnedMessageWindow, EdifactError>;
1083
1084    fn next(&mut self) -> Option<Self::Item> {
1085        if self.done {
1086            return None;
1087        }
1088        loop {
1089            let segment = match self.inner.next() {
1090                Some(Ok(s)) => s,
1091                Some(Err(e)) => {
1092                    self.done = true;
1093                    return Some(Err(e));
1094                }
1095                None => {
1096                    self.done = true;
1097                    // A window that opened (UNH seen) but never closed (no UNT)
1098                    // means the stream was truncated — surface as an error.
1099                    if self.in_message && !self.buf.is_empty() {
1100                        self.in_message = false;
1101                        let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
1102                        return Some(Err(EdifactError::UnexpectedEof { offset }));
1103                    }
1104                    return None;
1105                }
1106            };
1107
1108            match segment.tag.as_str() {
1109                "UNH" => {
1110                    if self.in_message {
1111                        // Malformed: new UNH without closing the prior UNT.
1112                        self.buf.clear();
1113                        self.in_message = false;
1114                        self.done = true;
1115                        return Some(Err(EdifactError::InvalidSegmentForMessage {
1116                            tag: "UNH".to_owned(),
1117                            message_type: "ENVELOPE".to_owned(),
1118                            span: segment.span,
1119                        }));
1120                    }
1121                    self.buf.clear();
1122                    self.in_message = true;
1123                    self.buf.push(segment);
1124                }
1125                "UNT" if self.in_message => {
1126                    self.buf.push(segment);
1127                    self.in_message = false;
1128                    let segments = std::mem::take(&mut self.buf);
1129                    return Some(Ok(OwnedMessageWindow::from_segments(segments)));
1130                }
1131                _ if self.in_message => {
1132                    self.buf.push(segment);
1133                }
1134                _ => {
1135                    // Envelope segment outside a window — skip.
1136                }
1137            }
1138        }
1139    }
1140}
1141
1142/// Stream-parse EDIFACT bytes into an iterator of per-message windows.
1143///
1144/// Each yielded [`MessageWindow`] spans one `UNH..UNT` pair, with segments
1145/// borrowing from `input` for their text content. Segment assembly is
1146/// zero-copy for borrowed input bytes; release-character unescaping may
1147/// allocate owned component strings when necessary.
1148/// Envelope segments (`UNB`, `UNZ`, …) are skipped automatically.
1149///
1150/// The `message_type` and `association_code` fields are populated directly from
1151/// the `UNH` segment so that routing logic does not need to traverse `segments`.
1152///
1153/// # Example
1154/// ```
1155/// use edifact_rs::from_bytes_windows;
1156/// let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'\
1157///               UNH+1+ORDERS:D:96A:UN'\
1158///               BGM+220+PO-001+9'\
1159///               UNT+3+1'\
1160///               UNZ+1+1'";
1161///
1162/// let windows: Vec<_> = from_bytes_windows(input)
1163///     .collect::<Result<_, _>>()
1164///     .unwrap();
1165/// assert_eq!(windows.len(), 1);
1166/// assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1167/// assert_eq!(windows[0].segments[0].tag, "UNH");
1168/// assert_eq!(windows[0].segments.last().unwrap().tag, "UNT");
1169/// ```
1170pub fn message_windows_bytes(input: &[u8]) -> MessageWindowsSliceIter<'_> {
1171    MessageWindowsSliceIter::new(crate::from_bytes(input))
1172}
1173
1174/// Stream-parse EDIFACT from a reader into an iterator of per-message windows.
1175///
1176/// Each yielded [`OwnedMessageWindow`] spans one `UNH..UNT` pair.
1177/// This variant reads lazily — only enough input to complete one window is
1178/// consumed per [`Iterator::next`] call.
1179pub fn message_windows_from_reader<R: Read>(
1180    reader: R,
1181) -> MessageWindowsIter<crate::FromReaderIter<R>> {
1182    MessageWindowsIter::new(crate::from_reader(reader))
1183}
1184
1185/// Stream typed messages from a reader by deserializing each `UNH..UNT` window.
1186///
1187/// This is the highest-level streaming API: it returns one `T` per message,
1188/// reading only as much data as needed to complete each window.
1189///
1190/// Each message window is deserialized via
1191/// [`EdifactDeserialize::edifact_deserialize_owned`], which avoids the
1192/// intermediate `Vec<Segment<'_>>` allocation incurred by the slice-based path.
1193/// Types derived with `#[derive(EdifactDeserialize)]` provide an efficient
1194/// override; manual implementations fall back to [`crate::OwnedSegment::as_borrowed`].
1195///
1196/// # Example
1197/// ```ignore
1198/// // Assuming `OrdersMessage` implements `EdifactDeserialize`:
1199/// let messages: Vec<OrdersMessage> =
1200///     deserialize_messages_from_reader::<OrdersMessage, _>(reader)
1201///         .collect::<Result<_, _>>()?;
1202/// ```
1203pub fn deserialize_messages_from_reader<T, R>(
1204    reader: R,
1205) -> impl Iterator<Item = Result<T, EdifactError>>
1206where
1207    T: EdifactDeserialize,
1208    R: Read,
1209{
1210    message_windows_from_reader(reader).map(|window| {
1211        let window = window?;
1212        T::edifact_deserialize_owned(&window.segments)
1213    })
1214}
1215
1216/// Stream typed messages from a byte slice by deserializing each `UNH..UNT` window.
1217pub fn deserialize_messages_bytes<T>(
1218    input: &[u8],
1219) -> impl Iterator<Item = Result<T, EdifactError>> + '_
1220where
1221    T: EdifactDeserialize,
1222{
1223    message_windows_bytes(input).map(|window| {
1224        let window = window?;
1225        T::edifact_deserialize(&window.segments)
1226    })
1227}
1228
1229// ── MessageDispatch ───────────────────────────────────────────────────────────
1230
1231/// A type-erased deserialized message produced by [`MessageDispatch`].
1232pub struct DispatchedMessage {
1233    /// The EDIFACT message type string extracted from the `UNH` segment.
1234    pub message_type: String,
1235    value: Box<dyn std::any::Any + Send + Sync>,
1236}
1237
1238impl DispatchedMessage {
1239    /// Attempt to downcast the inner value to `T`.
1240    ///
1241    /// Returns `None` if the stored type does not match `T`.
1242    pub fn downcast<T: std::any::Any + Send + Sync + 'static>(&self) -> Option<&T> {
1243        self.value.downcast_ref::<T>()
1244    }
1245}
1246
1247impl std::fmt::Debug for DispatchedMessage {
1248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1249        f.debug_struct("DispatchedMessage")
1250            .field("message_type", &self.message_type)
1251            .finish_non_exhaustive()
1252    }
1253}
1254
1255type DispatchHandlerFn = Box<
1256    dyn for<'a> Fn(&[Segment<'a>]) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1257        + Send
1258        + Sync,
1259>;
1260
1261type FallbackHandlerFn = Box<
1262    dyn for<'a> Fn(
1263            &[Segment<'a>],
1264            &str,
1265        ) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1266        + Send
1267        + Sync,
1268>;
1269
1270/// Type-based dispatcher for mixed-message EDIFACT streams.
1271///
1272/// Register one handler per message type with [`on`][Self::on], then call
1273/// [`dispatch`][Self::dispatch] on each message window.  If no handler matches
1274/// and a [`fallback`][Self::fallback] was registered it is invoked instead;
1275/// otherwise an [`EdifactError::UnexpectedMessageType`] is returned.
1276///
1277/// # Example
1278///
1279/// ```rust,ignore
1280/// let dispatch = MessageDispatch::new()
1281///     .on("ORDERS",  |segs| Orders::edifact_deserialize(segs))
1282///     .on("INVOIC",  |segs| Invoice::edifact_deserialize(segs));
1283///
1284/// for window in message_windows_bytes(input) {
1285///     let window = window?;
1286///     let msg = dispatch.dispatch(&window)?;
1287///     match msg.message_type.as_str() {
1288///         "ORDERS"  => { let o = msg.downcast::<Orders>().unwrap(); /* … */ }
1289///         "INVOIC"  => { let i = msg.downcast::<Invoice>().unwrap(); /* … */ }
1290///         _         => unreachable!(),
1291///     }
1292/// }
1293/// ```
1294pub struct MessageDispatch {
1295    handlers: Vec<(String, DispatchHandlerFn)>,
1296    fallback: Option<FallbackHandlerFn>,
1297}
1298
1299impl Default for MessageDispatch {
1300    fn default() -> Self {
1301        Self::new()
1302    }
1303}
1304
1305impl MessageDispatch {
1306    /// Create an empty dispatcher.
1307    pub fn new() -> Self {
1308        Self {
1309            handlers: Vec::new(),
1310            fallback: None,
1311        }
1312    }
1313
1314    /// Register a handler for `message_type`.
1315    ///
1316    /// The closure receives the full message window and returns a typed value
1317    /// that is boxed and stored inside [`DispatchedMessage`].
1318    pub fn on<T, F>(mut self, message_type: &str, handler: F) -> Self
1319    where
1320        T: std::any::Any + Send + Sync + 'static,
1321        F: for<'a> Fn(&[Segment<'a>]) -> Result<T, EdifactError> + Send + Sync + 'static,
1322    {
1323        let erased: DispatchHandlerFn = Box::new(move |segs| {
1324            let val = handler(segs)?;
1325            Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1326        });
1327        self.handlers.push((message_type.to_owned(), erased));
1328        self
1329    }
1330
1331    /// Register a fallback handler for unrecognised message types.
1332    ///
1333    /// The closure receives the segment window **and** the unknown message-type
1334    /// string.
1335    pub fn fallback<T, F>(mut self, handler: F) -> Self
1336    where
1337        T: std::any::Any + Send + Sync + 'static,
1338        F: for<'a> Fn(&[Segment<'a>], &str) -> Result<T, EdifactError> + Send + Sync + 'static,
1339    {
1340        let erased: FallbackHandlerFn = Box::new(move |segs, mt| {
1341            let val = handler(segs, mt)?;
1342            Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1343        });
1344        self.fallback = Some(erased);
1345        self
1346    }
1347
1348    /// Dispatch a single message window to the appropriate handler.
1349    ///
1350    /// The message type is extracted from the `UNH` segment.  If no `UNH` is
1351    /// present, [`EdifactError::MissingSegment`] is returned.
1352    pub fn dispatch(&self, window: &[Segment<'_>]) -> Result<DispatchedMessage, EdifactError> {
1353        let message_type = window
1354            .iter()
1355            .find(|s| s.tag == "UNH")
1356            .and_then(|unh| unh.get_element(1))
1357            .and_then(|e| e.get_component(0))
1358            .map(|s| s.to_owned())
1359            .ok_or_else(|| EdifactError::MissingSegment {
1360                tag: "UNH".to_owned(),
1361                expected_position: "first segment of message window".to_owned(),
1362            })?;
1363
1364        for (mt, handler) in &self.handlers {
1365            if *mt == message_type {
1366                let value = handler(window)?;
1367                return Ok(DispatchedMessage {
1368                    message_type,
1369                    value,
1370                });
1371            }
1372        }
1373
1374        if let Some(fallback) = &self.fallback {
1375            let value = fallback(window, &message_type)?;
1376            return Ok(DispatchedMessage {
1377                message_type,
1378                value,
1379            });
1380        }
1381
1382        Err(EdifactError::UnexpectedMessageType { message_type })
1383    }
1384
1385    /// Dispatch all messages from a byte reader.
1386    ///
1387    /// Each message window is extracted and dispatched in order.  The returned
1388    /// iterator is lazy — errors are yielded as `Err` items.
1389    pub fn dispatch_all_from_bytes<'a>(
1390        &'a self,
1391        input: &'a [u8],
1392    ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + 'a {
1393        message_windows_bytes(input).map(move |window| {
1394            let window = window?;
1395            self.dispatch(&window.segments)
1396        })
1397    }
1398
1399    /// Dispatch all messages from a reader.
1400    ///
1401    /// Parses the stream into message windows and dispatches each.  The
1402    /// returned iterator yields owned [`DispatchedMessage`] values lazily:
1403    /// each window is fully buffered in memory (as `Vec<OwnedSegment>`) before
1404    /// dispatch, but windows are processed one at a time rather than all at once.
1405    pub fn dispatch_all_from_reader<R: Read + 'static>(
1406        &self,
1407        reader: R,
1408    ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + '_ {
1409        message_windows_from_reader(reader).map(|window| {
1410            let window = window?;
1411            let borrowed: Vec<Segment<'_>> =
1412                window.segments.iter().map(|s| s.as_borrowed()).collect();
1413            self.dispatch(&borrowed)
1414        })
1415    }
1416}
1417
1418#[cfg(test)]
1419mod tests {
1420    use super::*;
1421
1422    // ── manual test impl ──────────────────────────────────────────────────────
1423    #[derive(Debug, PartialEq)]
1424    struct BgmSegment {
1425        doc_name_code: String,
1426        pruef_id: String,
1427        msg_function: Option<String>,
1428    }
1429
1430    impl EdifactSegmentTag for BgmSegment {
1431        const SEGMENT_TAG: &'static str = "BGM";
1432    }
1433
1434    struct NadM;
1435
1436    impl EdifactSegmentTag for NadM {
1437        const SEGMENT_TAG: &'static str = "NAD";
1438        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1439    }
1440
1441    struct NadWildcard;
1442
1443    impl EdifactSegmentTag for NadWildcard {
1444        const SEGMENT_TAG: &'static str = "NAD";
1445        const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1446    }
1447
1448    impl EdifactDeserialize for BgmSegment {
1449        fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
1450            let seg = find_segment(segments, "BGM").ok_or_else(|| {
1451                EdifactError::MissingRequiredElement {
1452                    tag: "BGM".to_owned(),
1453                    element_index: 0,
1454                }
1455            })?;
1456            Ok(Self {
1457                doc_name_code: element_str(seg, 0).to_owned(),
1458                pruef_id: element_str(seg, 1).to_owned(),
1459                msg_function: seg
1460                    .element_str(2)
1461                    .filter(|s| !s.is_empty())
1462                    .map(str::to_owned),
1463            })
1464        }
1465    }
1466
1467    #[test]
1468    fn deserialize_single_segment() {
1469        let input = b"BGM+E03+11042+9'";
1470        let bgm: BgmSegment = deserialize(input).unwrap();
1471        assert_eq!(bgm.doc_name_code, "E03");
1472        assert_eq!(bgm.pruef_id, "11042");
1473        assert_eq!(bgm.msg_function, Some("9".to_owned()));
1474    }
1475
1476    #[test]
1477    fn streaming_deserialize_first_from_bytes() {
1478        let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
1479        let bgm: BgmSegment = deserialize_first_streaming(input).unwrap();
1480        assert_eq!(bgm.pruef_id, "11042");
1481    }
1482
1483    #[test]
1484    fn streaming_deserialize_all_from_bytes() {
1485        let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
1486        let bgms: Vec<BgmSegment> = deserialize_all_streaming(input).unwrap();
1487        assert_eq!(bgms.len(), 2);
1488        assert_eq!(bgms[0].pruef_id, "11042");
1489        assert_eq!(bgms[1].pruef_id, "11043");
1490    }
1491
1492    #[test]
1493    fn streaming_deserialize_first_from_reader() {
1494        let input =
1495            std::io::Cursor::new(b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'".to_vec());
1496        let bgm: BgmSegment = deserialize_first_from_reader(input).unwrap();
1497        assert_eq!(bgm.pruef_id, "11042");
1498    }
1499
1500    #[test]
1501    fn streaming_deserialize_all_from_reader() {
1502        let input = std::io::Cursor::new(b"BGM+E03+11042+9'BGM+E01+11043+9'".to_vec());
1503        let bgms: Vec<BgmSegment> = deserialize_all_from_reader(input).unwrap();
1504        assert_eq!(bgms.len(), 2);
1505        assert_eq!(bgms[0].pruef_id, "11042");
1506        assert_eq!(bgms[1].pruef_id, "11043");
1507    }
1508
1509    #[test]
1510    fn missing_segment_returns_error() {
1511        let input = b"DTM+137:20230401:102'";
1512        let result: Result<BgmSegment, _> = deserialize(input);
1513        assert!(result.is_err());
1514    }
1515
1516    #[test]
1517    fn vec_collects_all_matching_segments() {
1518        let input = b"DTM+137:20230401:102'BGM+E03+11042+9'BGM+E01+11043+9'";
1519        let bgms: Vec<BgmSegment> = deserialize(input).unwrap();
1520        assert_eq!(bgms.len(), 2);
1521        assert_eq!(bgms[0].pruef_id, "11042");
1522        assert_eq!(bgms[1].pruef_id, "11043");
1523    }
1524
1525    #[test]
1526    fn find_qualified_segment_matches_qualifier() {
1527        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1528        let segments: Vec<Segment<'_>> =
1529            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1530        let nad_ms = find_qualified_segment(&segments, "NAD", "MS");
1531        let nad_mr = find_qualified_segment(&segments, "NAD", "MR");
1532        assert!(nad_ms.is_some());
1533        assert!(nad_mr.is_some());
1534        assert_eq!(element_str(nad_ms.unwrap(), 0), "MS");
1535        assert_eq!(element_str(nad_mr.unwrap(), 0), "MR");
1536    }
1537
1538    #[test]
1539    fn round_trip_str_api() {
1540        let input = "BGM+E03+11042+9'";
1541        let bgm: BgmSegment = deserialize_str(input).unwrap();
1542        assert_eq!(bgm.pruef_id, "11042");
1543    }
1544
1545    #[test]
1546    fn required_element_extraction() {
1547        let input = b"BGM+E03+11042+9'";
1548        let segments: Vec<Segment<'_>> =
1549            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1550        let seg = &segments[0];
1551
1552        assert_eq!(required_element(seg, 0).unwrap(), "E03");
1553        assert_eq!(required_element(seg, 1).unwrap(), "11042");
1554        // Element 5 doesn't exist
1555        assert!(required_element(seg, 5).is_err());
1556    }
1557
1558    #[test]
1559    fn optional_element_extraction() {
1560        let input = b"BGM+E03+11042+9'BGM+E01++absent'";
1561        let segments: Vec<Segment<'_>> =
1562            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1563
1564        // First segment
1565        assert_eq!(optional_element(&segments[0], 0), Some("E03"));
1566        assert_eq!(optional_element(&segments[0], 1), Some("11042"));
1567        assert_eq!(optional_element(&segments[0], 5), None);
1568
1569        // Second segment with empty element
1570        assert_eq!(optional_element(&segments[1], 1), None);
1571    }
1572
1573    #[test]
1574    fn component_extraction() {
1575        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1576        let segments: Vec<Segment<'_>> =
1577            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1578        let seg = &segments[0];
1579
1580        assert_eq!(required_component(seg, 0, 0).unwrap(), "UNOA");
1581        assert_eq!(required_component(seg, 0, 1).unwrap(), "1");
1582        // Non-existent component
1583        assert!(required_component(seg, 0, 5).is_err());
1584    }
1585
1586    #[test]
1587    fn composite_element_helper() {
1588        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1589        let segments: Vec<Segment<'_>> =
1590            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1591        let seg = &segments[0];
1592
1593        let comp = composite_element(seg, 0).unwrap();
1594        assert_eq!(comp.len(), 2);
1595        assert_eq!(comp.get(0), Some("UNOA"));
1596        assert_eq!(comp.get(1), Some("1"));
1597        assert_eq!(comp.get(5), None);
1598        assert_eq!(comp.get_or_empty(5), "");
1599    }
1600
1601    #[test]
1602    fn get_all_components() {
1603        // UNB has composite element: UNOA:1
1604        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1605        let segments: Vec<Segment<'_>> =
1606            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1607        let seg = &segments[0];
1608
1609        let comps: Vec<&str> = get_components_iter(seg, 0).collect(); // First element is UNOA:1
1610        assert!(!comps.is_empty(), "Expected components but got empty");
1611        assert_eq!(comps.len(), 2);
1612        assert_eq!(comps[0], "UNOA");
1613        assert_eq!(comps[1], "1");
1614    }
1615
1616    #[test]
1617    fn qualifier_pattern_matching_supports_exact_and_wildcard() {
1618        // Exact match (no wildcard)
1619        assert!(qualifier_matches_pattern("MS", "MS"));
1620        assert!(!qualifier_matches_pattern("MS", "M")); // Not a prefix match after R-003
1621        // Wildcard patterns
1622        assert!(qualifier_matches_pattern("MS", "M*"));
1623        assert!(qualifier_matches_pattern("MRY", "M*Y"));
1624        assert!(!qualifier_matches_pattern("AB", "M*"));
1625    }
1626
1627    /// Comprehensive table-driven tests for `qualifier_matches_pattern`.
1628    #[test]
1629    fn qualifier_matches_pattern_table() {
1630        // (value, pattern, expected)
1631        let cases: &[(&str, &str, bool)] = &[
1632            // ── empty inputs ────────────────────────────────────────────────
1633            ("", "", true),   // empty matches empty
1634            ("", "*", true),  // wildcard matches empty string
1635            ("A", "", false), // non-empty does not match empty pattern
1636            ("", "A", false), // empty does not match non-empty literal
1637            // ── literal (no wildcard) ────────────────────────────────────────
1638            ("MS", "MS", true),
1639            ("BY", "BY", true),
1640            ("ms", "MS", false),  // case-sensitive
1641            ("MSX", "MS", false), // prefix is NOT a match without wildcard
1642            ("M", "MS", false),   // too short
1643            // ── single wildcard at the end (prefix match) ────────────────────
1644            ("MS", "M*", true),
1645            ("MULTI", "MUL*", true),
1646            ("AB", "M*", false),
1647            ("", "M*", false), // empty does not start with 'M'
1648            // ── single wildcard at the start (suffix match) ──────────────────
1649            ("MSG", "*G", true),
1650            ("G", "*G", true),
1651            ("MSG", "*X", false),
1652            ("", "*G", false),
1653            // ── wildcard in the middle ───────────────────────────────────────
1654            ("MRY", "M*Y", true),
1655            ("MAY", "M*Y", true),
1656            ("MY", "M*Y", true),    // zero-width wildcard: "M" + "" + "Y"
1657            ("MYY", "M*Y", true),   // last 'Y' matches, wildcard = 'Y'
1658            ("MAYZ", "M*Y", false), // does not end with 'Y'
1659            ("AB", "M*Y", false),
1660            // ── bare wildcard (match-all) ────────────────────────────────────
1661            ("*", "*", true), // literal '*' value vs wildcard pattern
1662            ("anything", "*", true),
1663            ("", "*", true),
1664            // ── multiple wildcards ────────────────────────────────────────────
1665            ("ABCDE", "A*C*E", true),
1666            ("ACE", "A*C*E", true), // zero-width wildcards
1667            ("AXCYE", "A*C*E", true),
1668            ("ABCDF", "A*C*E", false),
1669            // ── wildcard with empty segment between stars ─────────────────────
1670            ("AB", "A**B", true), // "A**B" → parts ["A", "", "B"] → ends_with_wildcard?
1671            // ── pattern longer than value ─────────────────────────────────────
1672            ("AB", "A*B*C", false),
1673            // ── value contains pattern as substring but must anchor start ─────
1674            ("XMS", "MS", false),
1675        ];
1676
1677        for (value, pattern, expected) in cases {
1678            let got = qualifier_matches_pattern(value, pattern);
1679            assert_eq!(
1680                got, *expected,
1681                "qualifier_matches_pattern({value:?}, {pattern:?}) expected {expected} but got {got}"
1682            );
1683        }
1684    }
1685
1686    #[test]
1687    fn typed_qualifier_helpers_work() {
1688        let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1689        let segments: Vec<Segment<'_>> =
1690            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1691
1692        let first = find_segment_typed::<NadM>(&segments).unwrap();
1693        assert_eq!(first.element_str(0), Some("MS"));
1694
1695        let all: Vec<_> = find_segments_typed::<NadWildcard>(&segments).collect();
1696        assert_eq!(all.len(), 2);
1697    }
1698
1699    #[test]
1700    fn segment_accessor_trait_methods_work() {
1701        let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1702        let segments: Vec<Segment<'_>> =
1703            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1704        let seg = &segments[0];
1705
1706        assert_eq!(SegmentAccessor::get_element(seg, 1), Some("SENDER"));
1707        assert_eq!(SegmentAccessor::required_composite(seg, 0, 1).unwrap(), "1");
1708        let parsed: i32 = SegmentAccessor::code_element(seg, 4).unwrap();
1709        assert_eq!(parsed, 1);
1710        let reps = SegmentAccessor::component_range(seg, 3, 0, 2).unwrap();
1711        assert_eq!(reps, vec!["200101", "0900"]);
1712    }
1713
1714    #[test]
1715    fn group_helpers_detect_contiguity() {
1716        struct NadAny;
1717        impl EdifactSegmentTag for NadAny {
1718            const SEGMENT_TAG: &'static str = "NAD";
1719        }
1720
1721        let contiguous_input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'";
1722        let contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(contiguous_input)
1723            .collect::<Result<_, _>>()
1724            .unwrap();
1725        assert!(groups_are_contiguous_by_qualifier::<NadAny>(
1726            &contiguous_segments
1727        ));
1728
1729        let non_contiguous_input = b"NAD+MS+1'RFF+AA:1'NAD+MR+2'";
1730        let non_contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(non_contiguous_input)
1731            .collect::<Result<_, _>>()
1732            .unwrap();
1733        assert!(!groups_are_contiguous_by_qualifier::<NadAny>(
1734            &non_contiguous_segments
1735        ));
1736    }
1737
1738    #[test]
1739    fn group_helpers_collect_contiguous_groups() {
1740        struct NadAny;
1741        impl EdifactSegmentTag for NadAny {
1742            const SEGMENT_TAG: &'static str = "NAD";
1743        }
1744
1745        let input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'NAD+BY+3'";
1746        let segments: Vec<Segment<'_>> =
1747            crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1748        let groups = contiguous_groups_by_qualifier::<NadAny>(&segments);
1749
1750        assert_eq!(groups.len(), 2);
1751        assert_eq!(groups[0].len(), 2);
1752        assert_eq!(groups[1].len(), 1);
1753    }
1754
1755    // ── MessageWindowsIter tests ──────────────────────────────────────────────
1756
1757    #[test]
1758    fn message_windows_bytes_yields_complete_windows() {
1759        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1760                      UNH+1+ORDERS:D:96A:UN'\
1761                      BGM+220+PO-001+9'\
1762                      UNT+3+1'\
1763                      UNZ+1+1'";
1764        let windows: Vec<_> = message_windows_bytes(input)
1765            .collect::<Result<_, _>>()
1766            .unwrap();
1767        assert_eq!(windows.len(), 1);
1768        assert_eq!(windows[0].segments[0].tag, "UNH");
1769        assert_eq!(windows[0].segments.last().unwrap().tag, "UNT");
1770        assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1771        assert_eq!(windows[0].association_code.as_deref(), None);
1772    }
1773
1774    #[test]
1775    fn message_windows_bytes_preserves_owned_unh_metadata() {
1776        let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1777                      UNH+1+ORD?ERS:D:96A:UN:5??5??3a'\
1778                      BGM+220+PO-001+9'\
1779                      UNT+3+1'\
1780                      UNZ+1+1'";
1781        let windows: Vec<_> = message_windows_bytes(input)
1782            .collect::<Result<_, _>>()
1783            .unwrap();
1784
1785        assert_eq!(windows.len(), 1);
1786        assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1787        assert_eq!(windows[0].association_code.as_deref(), Some("5?5?3a"));
1788    }
1789
1790    #[test]
1791    fn message_windows_truncated_stream_returns_error() {
1792        // Stream ends after UNH and BGM but without UNT — truncation must be an error
1793        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1794        let results: Vec<_> = message_windows_bytes(input).collect();
1795        assert_eq!(results.len(), 1);
1796        assert!(
1797            matches!(results[0], Err(EdifactError::UnexpectedEof { .. })),
1798            "expected UnexpectedEof for truncated window, got: {:?}",
1799            results[0]
1800        );
1801    }
1802
1803    #[test]
1804    fn message_windows_subsequent_calls_return_none_after_truncation() {
1805        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1806        let mut iter = message_windows_bytes(input);
1807        assert!(matches!(
1808            iter.next(),
1809            Some(Err(EdifactError::UnexpectedEof { .. }))
1810        ));
1811        // After the error, the iterator must be fused (done = true)
1812        assert!(iter.next().is_none());
1813    }
1814
1815    #[test]
1816    fn message_windows_unh_without_unt_before_next_unh_returns_error() {
1817        let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'\
1818                      UNH+2+ORDERS:D:96A:UN'BGM+220+PO-002+9'UNT+3+2'";
1819        let results: Vec<_> = message_windows_bytes(input).collect();
1820        // First item must be an error (UNH before UNT — missing closer)
1821        assert!(
1822            matches!(
1823                results[0],
1824                Err(EdifactError::InvalidSegmentForMessage { ref tag, .. }) if tag == "UNH"
1825            ),
1826            "expected InvalidSegmentForMessage(UNH), got: {:?}",
1827            results[0]
1828        );
1829    }
1830
1831    // ── SegmentAccessor unit tests ─────────────────────────────────────────────
1832
1833    fn parse_one(input: &str) -> crate::OwnedSegment {
1834        crate::from_reader_collect(std::io::Cursor::new(input.as_bytes()))
1835            .expect("parse failed")
1836            .into_iter()
1837            .next()
1838            .expect("at least one segment")
1839    }
1840
1841    #[test]
1842    fn segment_accessor_get_element_returns_value() {
1843        let owned = parse_one("BGM+220+PO-001+9'");
1844        let seg = owned.as_borrowed();
1845        assert_eq!(SegmentAccessor::get_element(&seg, 0), Some("220"));
1846        assert_eq!(SegmentAccessor::get_element(&seg, 1), Some("PO-001"));
1847        assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("9"));
1848        assert_eq!(
1849            SegmentAccessor::get_element(&seg, 9),
1850            None,
1851            "out-of-bounds must return None"
1852        );
1853    }
1854
1855    #[test]
1856    fn segment_accessor_get_element_filters_empty() {
1857        let owned = parse_one("TST+++VALUE'");
1858        let seg = owned.as_borrowed();
1859        // elements 0 and 1 are empty; element 2 is "VALUE"
1860        assert_eq!(
1861            SegmentAccessor::get_element(&seg, 0),
1862            None,
1863            "empty element must return None"
1864        );
1865        assert_eq!(
1866            SegmentAccessor::get_element(&seg, 1),
1867            None,
1868            "empty element must return None"
1869        );
1870        assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("VALUE"));
1871    }
1872
1873    #[test]
1874    fn segment_accessor_get_component_returns_value() {
1875        let owned = parse_one("UNH+1+ORDERS:D:96A:UN'");
1876        let seg = owned.as_borrowed();
1877        assert_eq!(seg.get_component(1, 0), Some("ORDERS"));
1878        assert_eq!(seg.get_component(1, 1), Some("D"));
1879        assert_eq!(seg.get_component(1, 2), Some("96A"));
1880        assert_eq!(seg.get_component(1, 3), Some("UN"));
1881        assert_eq!(
1882            seg.get_component(1, 9),
1883            None,
1884            "out-of-bounds must return None"
1885        );
1886    }
1887
1888    #[test]
1889    fn segment_accessor_text_element_errors_on_missing() {
1890        let owned = parse_one("BGM+'");
1891        let seg = owned.as_borrowed();
1892        // element 0 is empty — text_element must return an error
1893        let err = seg.text_element(0);
1894        assert!(
1895            matches!(err, Err(EdifactError::MissingRequiredElement { ref tag, element_index: 0 }) if tag == "BGM"),
1896            "expected MissingRequiredElement, got: {err:?}"
1897        );
1898    }
1899
1900    #[test]
1901    fn segment_accessor_required_composite_errors_on_missing() {
1902        let owned = parse_one("DTM+137'");
1903        let seg = owned.as_borrowed();
1904        // component 1 of element 0 is absent
1905        let err = seg.required_composite(0, 1);
1906        assert!(
1907            matches!(err, Err(EdifactError::MissingRequiredComponent { ref tag, element_index: 0, component_index: 1 }) if tag == "DTM"),
1908            "expected MissingRequiredComponent, got: {err:?}"
1909        );
1910    }
1911
1912    #[test]
1913    fn segment_accessor_code_element_parses_integer() {
1914        let owned = parse_one("QTY+21:100'");
1915        let seg = owned.as_borrowed();
1916        let qty: u32 = seg.code_element(0).expect("should parse qualifier as u32");
1917        assert_eq!(qty, 21);
1918    }
1919
1920    #[test]
1921    fn segment_accessor_optional_element_absent_returns_none() {
1922        let owned = parse_one("BGM+220'");
1923        let seg = owned.as_borrowed();
1924        assert_eq!(seg.optional_element(5), None);
1925    }
1926}