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