1use crate::{EdifactError, Segment};
9use std::borrow::Cow;
10use std::io::Read;
11use std::str::FromStr;
12
13pub trait EdifactDeserialize: Sized {
20 fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError>;
25
26 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
50pub trait EdifactCompositeDeserialize: Sized {
55 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
68pub trait EdifactSegmentTag {
73 const SEGMENT_TAG: &'static str;
75
76 const QUALIFIER_PATTERN: Option<&'static str> = None;
82
83 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 fn matches_segment(seg: &Segment<'_>) -> bool {
98 seg.tag == Self::SEGMENT_TAG && Self::matches_qualifier(seg)
99 }
100
101 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
123impl<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
148pub fn deserialize<T: EdifactDeserialize>(input: &[u8]) -> Result<T, EdifactError> {
158 let segments: Vec<Segment<'_>> = crate::from_bytes(input).collect::<Result<_, _>>()?;
159 T::edifact_deserialize(&segments)
160}
161
162pub fn deserialize_first_streaming<T>(input: &[u8]) -> Result<T, EdifactError>
167where
168 T: EdifactDeserialize + EdifactSegmentTag,
169{
170 for segment in crate::from_bytes(input) {
171 let segment = segment?;
172 if T::matches_segment(&segment) {
173 return T::edifact_deserialize(std::slice::from_ref(&segment));
174 }
175 }
176
177 Err(EdifactError::MissingSegment {
178 tag: T::SEGMENT_TAG.to_owned(),
179 expected_position: "any position in input".to_owned(),
180 })
181}
182
183pub fn deserialize_all_streaming<T>(input: &[u8]) -> Result<Vec<T>, EdifactError>
187where
188 T: EdifactDeserialize + EdifactSegmentTag,
189{
190 let mut out = Vec::new();
191 for segment in crate::from_bytes(input) {
192 let segment = segment?;
193 if T::matches_segment(&segment) {
194 out.push(T::edifact_deserialize(std::slice::from_ref(&segment))?);
195 }
196 }
197 Ok(out)
198}
199
200pub fn deserialize_first_from_reader<T, R>(reader: R) -> Result<T, EdifactError>
204where
205 T: EdifactDeserialize + EdifactSegmentTag,
206 R: Read,
207{
208 for segment in crate::from_reader_iter(reader) {
209 let segment = segment?;
210 if !T::matches_owned_segment(&segment) {
212 continue;
213 }
214 return T::edifact_deserialize_owned(std::slice::from_ref(&segment));
215 }
216
217 Err(EdifactError::MissingSegment {
218 tag: T::SEGMENT_TAG.to_owned(),
219 expected_position: "any position in input".to_owned(),
220 })
221}
222
223pub fn deserialize_all_from_reader<T, R>(reader: R) -> Result<Vec<T>, EdifactError>
225where
226 T: EdifactDeserialize + EdifactSegmentTag,
227 R: Read,
228{
229 let mut out = Vec::new();
230 for segment in crate::from_reader_iter(reader) {
231 let segment = segment?;
232 if !T::matches_owned_segment(&segment) {
234 continue;
235 }
236 out.push(T::edifact_deserialize_owned(std::slice::from_ref(
237 &segment,
238 ))?);
239 }
240 Ok(out)
241}
242
243pub fn deserialize_str<T: EdifactDeserialize>(input: &str) -> Result<T, EdifactError> {
245 deserialize(input.as_bytes())
246}
247
248pub fn find_segment<'s, 'd>(segments: &'s [Segment<'d>], tag: &str) -> Option<&'s Segment<'d>> {
252 segments.iter().find(|s| s.tag == tag)
253}
254
255pub fn find_segments_iter<'s, 'd: 's>(
257 segments: &'s [Segment<'d>],
258 tag: &'s str,
259) -> impl Iterator<Item = &'s Segment<'d>> {
260 segments.iter().filter(move |s| s.tag == tag)
261}
262
263pub fn find_qualified_segment<'s, 'd>(
265 segments: &'s [Segment<'d>],
266 tag: &str,
267 qualifier: &str,
268) -> Option<&'s Segment<'d>> {
269 segments
270 .iter()
271 .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
272}
273
274pub fn find_segment_typed<'s, 'd, T>(segments: &'s [Segment<'d>]) -> Option<&'s Segment<'d>>
276where
277 T: EdifactSegmentTag,
278{
279 segments.iter().find(|s| T::matches_segment(s))
280}
281
282pub fn find_segments_typed<'s, 'd: 's, T>(
284 segments: &'s [Segment<'d>],
285) -> impl Iterator<Item = &'s Segment<'d>>
286where
287 T: EdifactSegmentTag,
288{
289 segments.iter().filter(|s| T::matches_segment(s))
290}
291
292pub fn contiguous_groups_by_qualifier<'s, 'd, T>(
297 segments: &'s [Segment<'d>],
298) -> Vec<&'s [Segment<'d>]>
299where
300 T: EdifactSegmentTag,
301{
302 let mut groups = Vec::new();
303 let mut idx = 0;
304 while idx < segments.len() {
305 if T::matches_segment(&segments[idx]) {
306 let start = idx;
307 idx += 1;
308 while idx < segments.len() && T::matches_segment(&segments[idx]) {
309 idx += 1;
310 }
311 groups.push(&segments[start..idx]);
312 } else {
313 idx += 1;
314 }
315 }
316 groups
317}
318
319pub fn contiguous_groups_iter<'s, 'd, T>(
336 segments: &'s [Segment<'d>],
337) -> impl Iterator<Item = &'s [Segment<'d>]> + 's
338where
339 T: EdifactSegmentTag,
340{
341 let mut idx = 0;
342 let len = segments.len();
343 std::iter::from_fn(move || {
344 while idx < len && !T::matches_segment(&segments[idx]) {
346 idx += 1;
347 }
348 if idx >= len {
349 return None;
350 }
351 let start = idx;
352 idx += 1;
353 while idx < len && T::matches_segment(&segments[idx]) {
354 idx += 1;
355 }
356 Some(&segments[start..idx])
357 })
358}
359
360pub fn groups_are_contiguous_by_qualifier<T>(segments: &[Segment<'_>]) -> bool
362where
363 T: EdifactSegmentTag,
364{
365 let mut seen_match = false;
366 let mut seen_gap_after_match = false;
367
368 for seg in segments {
369 if T::matches_segment(seg) {
370 if seen_gap_after_match {
371 return false;
372 }
373 seen_match = true;
374 } else if seen_match {
375 seen_gap_after_match = true;
376 }
377 }
378
379 true
380}
381
382pub fn qualifier_matches_pattern(value: &str, pattern: &str) -> bool {
391 if pattern.is_empty() {
392 return value.is_empty();
393 }
394
395 if !pattern.contains('*') {
396 return value == pattern;
397 }
398
399 if let Some((prefix, suffix)) = pattern.split_once('*') {
401 if !pattern[prefix.len() + 1..].contains('*') {
403 return value.len() >= prefix.len() + suffix.len()
404 && value.starts_with(prefix)
405 && value.ends_with(suffix)
406 && {
407 let mid_start = prefix.len();
409 let mid_end = value.len().saturating_sub(suffix.len());
410 mid_start <= mid_end
411 };
412 }
413 }
414
415 let parts: smallvec::SmallVec<[&str; 4]> = pattern.split('*').collect();
417 let prefix = parts[0];
418 let suffix = parts[parts.len() - 1];
419
420 if !value.starts_with(prefix) || !value.ends_with(suffix) {
421 return false;
422 }
423
424 let mid_start = prefix.len();
425 let mid_end = value.len().saturating_sub(suffix.len());
426
427 if mid_start > mid_end {
428 return parts[1..parts.len() - 1].iter().all(|p| p.is_empty());
429 }
430
431 let mut remaining = &value[mid_start..mid_end];
432
433 for part in &parts[1..parts.len() - 1] {
434 if part.is_empty() {
435 continue;
436 }
437 match remaining.find(part) {
438 Some(idx) => remaining = &remaining[idx + part.len()..],
439 None => return false,
440 }
441 }
442
443 true
444}
445
446#[inline]
448pub fn element_str<'s>(seg: &'s Segment<'_>, idx: usize) -> &'s str {
449 seg.element_str(idx).unwrap_or("")
450}
451
452pub fn required_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Result<&'a str, EdifactError> {
468 seg.text_element(idx)
469}
470
471pub fn optional_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Option<&'a str> {
477 SegmentAccessor::optional_element(seg, idx)
478}
479
480pub fn required_component<'a>(
498 seg: &'a Segment<'_>,
499 elem_idx: usize,
500 comp_idx: usize,
501) -> Result<&'a str, EdifactError> {
502 seg.required_composite(elem_idx, comp_idx)
503}
504
505pub fn optional_component<'a>(
511 seg: &'a Segment<'_>,
512 elem_idx: usize,
513 comp_idx: usize,
514) -> Option<&'a str> {
515 SegmentAccessor::get_component(seg, elem_idx, comp_idx)
516}
517
518pub fn get_components_iter<'a>(seg: &'a Segment<'_>, idx: usize) -> impl Iterator<Item = &'a str> {
522 seg.elements
523 .get(idx)
524 .into_iter()
525 .flat_map(|elem| elem.components.iter().map(|c| c.as_ref()))
526}
527
528pub struct CompositeElement<'a> {
530 components: &'a [std::borrow::Cow<'a, str>],
531}
532
533impl<'a> CompositeElement<'a> {
534 pub fn get(&self, i: usize) -> Option<&'a str> {
536 self.components.get(i).map(|c| c.as_ref())
537 }
538
539 pub fn get_or_empty(&self, i: usize) -> &'a str {
541 self.get(i).unwrap_or("")
542 }
543
544 pub fn len(&self) -> usize {
546 self.components.len()
547 }
548
549 pub fn is_empty(&self) -> bool {
551 self.components.is_empty()
552 }
553
554 pub fn iter(&self) -> impl Iterator<Item = &'a str> {
556 self.components.iter().map(|c| c.as_ref())
557 }
558
559 pub fn from_slice(components: &'a [std::borrow::Cow<'a, str>]) -> Self {
564 Self { components }
565 }
566}
567
568pub fn composite_element<'a>(seg: &'a Segment<'_>, idx: usize) -> Option<CompositeElement<'a>> {
570 seg.elements.get(idx).map(|elem| CompositeElement {
571 components: &elem.components,
572 })
573}
574
575pub fn find_segment_owned<'s>(
582 segments: &'s [crate::OwnedSegment],
583 tag: &str,
584) -> Option<&'s crate::OwnedSegment> {
585 segments.iter().find(|s| s.tag == tag)
586}
587
588pub fn find_qualified_segment_owned<'s>(
596 segments: &'s [crate::OwnedSegment],
597 tag: &str,
598 qualifier: &str,
599) -> Option<&'s crate::OwnedSegment> {
600 segments
601 .iter()
602 .find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
603}
604
605pub trait SegmentAccessor<'a> {
607 fn get_element(&'a self, idx: usize) -> Option<&'a str>;
609 fn get_component(&'a self, elem: usize, comp: usize) -> Option<&'a str>;
611 fn get_composite(&'a self, idx: usize) -> Option<CompositeElement<'a>>;
613
614 fn text_element(&'a self, idx: usize) -> Result<&'a str, EdifactError>;
616 fn optional_element(&'a self, idx: usize) -> Option<&'a str>;
618 fn code_element<T: FromStr>(&'a self, idx: usize) -> Result<T, EdifactError>;
620 fn required_composite(&'a self, elem: usize, comp: usize) -> Result<&'a str, EdifactError>;
622 fn repeating_components(
628 &'a self,
629 elem: usize,
630 start_idx: usize,
631 count: usize,
632 ) -> Result<Vec<&'a str>, EdifactError> {
633 self.repeating_components_iter(elem, start_idx, count)
636 .collect()
637 }
638
639 fn repeating_components_iter(
644 &'a self,
645 elem: usize,
646 start_idx: usize,
647 count: usize,
648 ) -> impl Iterator<Item = Result<&'a str, EdifactError>> + 'a;
649}
650
651impl<'s, 'd> SegmentAccessor<'s> for Segment<'d>
652where
653 'd: 's,
654{
655 fn get_element(&'s self, idx: usize) -> Option<&'s str> {
656 self.element_str(idx).filter(|s| !s.is_empty())
657 }
658
659 fn get_component(&'s self, elem: usize, comp: usize) -> Option<&'s str> {
660 self.elements
661 .get(elem)
662 .and_then(|e| e.get_component(comp))
663 .filter(|s| !s.is_empty())
664 }
665
666 fn get_composite(&'s self, idx: usize) -> Option<CompositeElement<'s>> {
667 composite_element(self, idx)
668 }
669
670 fn text_element(&'s self, idx: usize) -> Result<&'s str, EdifactError> {
671 <Self as SegmentAccessor>::get_element(self, idx).ok_or_else(|| {
672 EdifactError::MissingRequiredElement {
673 tag: self.tag.to_owned(),
674 element_index: idx,
675 }
676 })
677 }
678
679 fn optional_element(&'s self, idx: usize) -> Option<&'s str> {
680 <Self as SegmentAccessor>::get_element(self, idx)
681 }
682
683 fn code_element<T: FromStr>(&'s self, idx: usize) -> Result<T, EdifactError> {
684 let raw = self.text_element(idx)?;
685 raw.parse::<T>().map_err(|_| EdifactError::InvalidText {
686 offset: self
687 .element_span(idx)
688 .map(|s| s.start)
689 .unwrap_or(self.span.start),
690 })
691 }
692
693 fn required_composite(&'s self, elem: usize, comp: usize) -> Result<&'s str, EdifactError> {
694 match self.elements.get(elem) {
695 None => Err(EdifactError::MissingRequiredElement {
696 tag: self.tag.to_owned(),
697 element_index: elem,
698 }),
699 Some(e) => e
700 .get_component(comp)
701 .filter(|s| !s.is_empty())
702 .ok_or_else(|| EdifactError::MissingRequiredComponent {
703 tag: self.tag.to_owned(),
704 element_index: elem,
705 component_index: comp,
706 }),
707 }
708 }
709
710 fn repeating_components_iter(
711 &'s self,
712 elem: usize,
713 start_idx: usize,
714 count: usize,
715 ) -> impl Iterator<Item = Result<&'s str, EdifactError>> + 's {
716 let tag = self.tag;
717 let element_exists = self.elements.get(elem).is_some();
718 let components = self
719 .elements
720 .get(elem)
721 .map(|e| e.components.as_slice())
722 .unwrap_or(&[]);
723 (start_idx..start_idx + count).map(move |idx| {
724 components
725 .get(idx)
726 .map(|c| c.as_ref())
727 .filter(|s| !s.is_empty())
728 .ok_or_else(|| {
729 if element_exists {
730 EdifactError::MissingRequiredComponent {
731 tag: tag.to_owned(),
732 element_index: elem,
733 component_index: idx,
734 }
735 } else {
736 EdifactError::MissingRequiredElement {
737 tag: tag.to_owned(),
738 element_index: elem,
739 }
740 }
741 })
742 })
743 }
744}
745
746#[derive(Debug)]
768pub struct MessageWindow<'a> {
769 pub message_type: Option<Cow<'a, str>>,
774 pub association_code: Option<Cow<'a, str>>,
779 pub segments: Vec<crate::Segment<'a>>,
781}
782
783impl<'a> MessageWindow<'a> {
784 fn from_segments(segments: Vec<crate::Segment<'a>>) -> Self {
790 let message_type = segments
791 .first()
792 .filter(|s| s.tag == "UNH")
793 .and_then(|unh| unh_component(unh, 0));
794 let association_code = segments
795 .first()
796 .filter(|s| s.tag == "UNH")
797 .and_then(|unh| unh_component(unh, 4));
798 Self {
799 message_type,
800 association_code,
801 segments,
802 }
803 }
804}
805
806fn unh_component<'a, 'b>(seg: &'b crate::Segment<'a>, comp_idx: usize) -> Option<Cow<'a, str>>
814where
815 'a: 'b,
816{
817 seg.elements
818 .get(1)
819 .and_then(|e| e.components.get(comp_idx))
820 .and_then(|c| if c.is_empty() { None } else { Some(c.clone()) })
821}
822
823#[derive(Debug, Clone)]
832pub struct OwnedMessageWindow {
833 pub message_type: Option<String>,
835 pub association_code: Option<String>,
837 pub segments: Vec<crate::OwnedSegment>,
839}
840
841impl OwnedMessageWindow {
842 fn from_segments(segments: Vec<crate::OwnedSegment>) -> Self {
843 let unh = segments.first().filter(|s| s.tag == "UNH");
844 let message_type = unh
845 .and_then(|s| s.elements.get(1))
846 .and_then(|e| e.components.first())
847 .map(|c| c.as_ref())
848 .filter(|s: &&str| !s.is_empty())
849 .map(str::to_owned);
850 let association_code = unh
851 .and_then(|s| s.elements.get(1))
852 .and_then(|e| e.components.get(4))
853 .map(|c| c.as_ref())
854 .filter(|s: &&str| !s.is_empty())
855 .map(str::to_owned);
856 Self {
857 message_type,
858 association_code,
859 segments,
860 }
861 }
862}
863
864pub struct MessageWindowsSliceIter<'a> {
874 inner: crate::FromBytesIter<'a>,
875 buf: Vec<crate::Segment<'a>>,
876 in_message: bool,
877 done: bool,
878}
879
880impl<'a> MessageWindowsSliceIter<'a> {
881 fn new(inner: crate::FromBytesIter<'a>) -> Self {
882 Self {
883 inner,
884 buf: Vec::new(),
885 in_message: false,
886 done: false,
887 }
888 }
889}
890
891impl<'a> Iterator for MessageWindowsSliceIter<'a> {
892 type Item = Result<MessageWindow<'a>, EdifactError>;
893
894 fn next(&mut self) -> Option<Self::Item> {
895 if self.done {
896 return None;
897 }
898 loop {
899 let segment = match self.inner.next() {
900 Some(Ok(s)) => s,
901 Some(Err(e)) => {
902 self.done = true;
903 return Some(Err(e));
904 }
905 None => {
906 self.done = true;
907 if self.in_message && !self.buf.is_empty() {
908 self.in_message = false;
909 let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
910 return Some(Err(EdifactError::UnexpectedEof { offset }));
911 }
912 return None;
913 }
914 };
915
916 match segment.tag {
917 "UNH" => {
918 if self.in_message {
919 self.buf.clear();
920 self.in_message = false;
921 self.done = true;
922 let offset = segment.span.start;
923 return Some(Err(EdifactError::InvalidSegmentForMessage {
924 tag: "UNH".to_owned(),
925 message_type: "ENVELOPE".to_owned(),
926 offset,
927 }));
928 }
929 self.buf.clear();
930 self.in_message = true;
931 self.buf.push(segment);
932 }
933 "UNT" if self.in_message => {
934 self.buf.push(segment);
935 self.in_message = false;
936 let segments = std::mem::take(&mut self.buf);
937 return Some(Ok(MessageWindow::from_segments(segments)));
938 }
939 _ if self.in_message => {
940 self.buf.push(segment);
941 }
942 _ => {
943 }
945 }
946 }
947 }
948}
949
950pub struct MessageWindowsIter<I> {
971 inner: I,
972 buf: Vec<crate::OwnedSegment>,
973 in_message: bool,
974 done: bool,
977}
978
979impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> MessageWindowsIter<I> {
980 pub fn new(inner: I) -> Self {
982 Self {
983 inner,
984 buf: Vec::new(),
985 in_message: false,
986 done: false,
987 }
988 }
989}
990
991impl<I: Iterator<Item = Result<crate::OwnedSegment, EdifactError>>> Iterator
992 for MessageWindowsIter<I>
993{
994 type Item = Result<OwnedMessageWindow, EdifactError>;
995
996 fn next(&mut self) -> Option<Self::Item> {
997 if self.done {
998 return None;
999 }
1000 loop {
1001 let segment = match self.inner.next() {
1002 Some(Ok(s)) => s,
1003 Some(Err(e)) => {
1004 self.done = true;
1005 return Some(Err(e));
1006 }
1007 None => {
1008 self.done = true;
1009 if self.in_message && !self.buf.is_empty() {
1012 self.in_message = false;
1013 let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
1014 return Some(Err(EdifactError::UnexpectedEof { offset }));
1015 }
1016 return None;
1017 }
1018 };
1019
1020 match segment.tag.as_str() {
1021 "UNH" => {
1022 if self.in_message {
1023 self.buf.clear();
1025 self.in_message = false;
1026 self.done = true;
1027 let offset = segment.span.start;
1028 return Some(Err(EdifactError::InvalidSegmentForMessage {
1029 tag: "UNH".to_owned(),
1030 message_type: "ENVELOPE".to_owned(),
1031 offset,
1032 }));
1033 }
1034 self.buf.clear();
1035 self.in_message = true;
1036 self.buf.push(segment);
1037 }
1038 "UNT" if self.in_message => {
1039 self.buf.push(segment);
1040 self.in_message = false;
1041 let segments = std::mem::take(&mut self.buf);
1042 return Some(Ok(OwnedMessageWindow::from_segments(segments)));
1043 }
1044 _ if self.in_message => {
1045 self.buf.push(segment);
1046 }
1047 _ => {
1048 }
1050 }
1051 }
1052 }
1053}
1054
1055pub fn message_windows_bytes(input: &[u8]) -> MessageWindowsSliceIter<'_> {
1084 MessageWindowsSliceIter::new(crate::from_bytes(input))
1085}
1086
1087pub fn message_windows_from_reader<R: Read>(
1093 reader: R,
1094) -> MessageWindowsIter<crate::FromReaderIter<R>> {
1095 MessageWindowsIter::new(crate::from_reader_iter(reader))
1096}
1097
1098pub fn deserialize_messages_from_reader<T, R>(
1117 reader: R,
1118) -> impl Iterator<Item = Result<T, EdifactError>>
1119where
1120 T: EdifactDeserialize,
1121 R: Read,
1122{
1123 message_windows_from_reader(reader).map(|window| {
1124 let window = window?;
1125 T::edifact_deserialize_owned(&window.segments)
1126 })
1127}
1128
1129pub fn deserialize_messages_bytes<T>(
1131 input: &[u8],
1132) -> impl Iterator<Item = Result<T, EdifactError>> + '_
1133where
1134 T: EdifactDeserialize,
1135{
1136 message_windows_bytes(input).map(|window| {
1137 let window = window?;
1138 T::edifact_deserialize(&window.segments)
1139 })
1140}
1141
1142pub struct DispatchedMessage {
1146 pub message_type: String,
1148 value: Box<dyn std::any::Any + Send + Sync>,
1149}
1150
1151impl DispatchedMessage {
1152 pub fn downcast<T: std::any::Any + Send + Sync + 'static>(&self) -> Option<&T> {
1156 self.value.downcast_ref::<T>()
1157 }
1158}
1159
1160impl std::fmt::Debug for DispatchedMessage {
1161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1162 f.debug_struct("DispatchedMessage")
1163 .field("message_type", &self.message_type)
1164 .finish_non_exhaustive()
1165 }
1166}
1167
1168type DispatchHandlerFn = Box<
1169 dyn for<'a> Fn(&[Segment<'a>]) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1170 + Send
1171 + Sync,
1172>;
1173
1174type FallbackHandlerFn = Box<
1175 dyn for<'a> Fn(
1176 &[Segment<'a>],
1177 &str,
1178 ) -> Result<Box<dyn std::any::Any + Send + Sync>, EdifactError>
1179 + Send
1180 + Sync,
1181>;
1182
1183pub struct MessageDispatch {
1208 handlers: Vec<(String, DispatchHandlerFn)>,
1209 fallback: Option<FallbackHandlerFn>,
1210}
1211
1212impl Default for MessageDispatch {
1213 fn default() -> Self {
1214 Self::new()
1215 }
1216}
1217
1218impl MessageDispatch {
1219 pub fn new() -> Self {
1221 Self {
1222 handlers: Vec::new(),
1223 fallback: None,
1224 }
1225 }
1226
1227 pub fn on<T, F>(mut self, message_type: &str, handler: F) -> Self
1232 where
1233 T: std::any::Any + Send + Sync + 'static,
1234 F: for<'a> Fn(&[Segment<'a>]) -> Result<T, EdifactError> + Send + Sync + 'static,
1235 {
1236 let erased: DispatchHandlerFn = Box::new(move |segs| {
1237 let val = handler(segs)?;
1238 Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1239 });
1240 self.handlers.push((message_type.to_owned(), erased));
1241 self
1242 }
1243
1244 pub fn fallback<T, F>(mut self, handler: F) -> Self
1249 where
1250 T: std::any::Any + Send + Sync + 'static,
1251 F: for<'a> Fn(&[Segment<'a>], &str) -> Result<T, EdifactError> + Send + Sync + 'static,
1252 {
1253 let erased: FallbackHandlerFn = Box::new(move |segs, mt| {
1254 let val = handler(segs, mt)?;
1255 Ok(Box::new(val) as Box<dyn std::any::Any + Send + Sync>)
1256 });
1257 self.fallback = Some(erased);
1258 self
1259 }
1260
1261 pub fn dispatch(&self, window: &[Segment<'_>]) -> Result<DispatchedMessage, EdifactError> {
1266 let message_type = window
1267 .iter()
1268 .find(|s| s.tag == "UNH")
1269 .and_then(|unh| unh.get_element(1))
1270 .and_then(|e| e.get_component(0))
1271 .map(|s| s.to_owned())
1272 .ok_or_else(|| EdifactError::MissingSegment {
1273 tag: "UNH".to_owned(),
1274 expected_position: "first segment of message window".to_owned(),
1275 })?;
1276
1277 for (mt, handler) in &self.handlers {
1278 if *mt == message_type {
1279 let value = handler(window)?;
1280 return Ok(DispatchedMessage {
1281 message_type,
1282 value,
1283 });
1284 }
1285 }
1286
1287 if let Some(fallback) = &self.fallback {
1288 let value = fallback(window, &message_type)?;
1289 return Ok(DispatchedMessage {
1290 message_type,
1291 value,
1292 });
1293 }
1294
1295 Err(EdifactError::UnexpectedMessageType { message_type })
1296 }
1297
1298 pub fn dispatch_all_from_bytes<'a>(
1303 &'a self,
1304 input: &'a [u8],
1305 ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + 'a {
1306 message_windows_bytes(input).map(move |window| {
1307 let window = window?;
1308 self.dispatch(&window.segments)
1309 })
1310 }
1311
1312 pub fn dispatch_all_from_reader<R: Read + 'static>(
1319 &self,
1320 reader: R,
1321 ) -> impl Iterator<Item = Result<DispatchedMessage, EdifactError>> + '_ {
1322 message_windows_from_reader(reader).map(|window| {
1323 let window = window?;
1324 let borrowed: Vec<Segment<'_>> =
1325 window.segments.iter().map(|s| s.as_borrowed()).collect();
1326 self.dispatch(&borrowed)
1327 })
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use super::*;
1334
1335 #[derive(Debug, PartialEq)]
1337 struct BgmSegment {
1338 doc_name_code: String,
1339 pruef_id: String,
1340 msg_function: Option<String>,
1341 }
1342
1343 impl EdifactSegmentTag for BgmSegment {
1344 const SEGMENT_TAG: &'static str = "BGM";
1345 }
1346
1347 struct NadM;
1348
1349 impl EdifactSegmentTag for NadM {
1350 const SEGMENT_TAG: &'static str = "NAD";
1351 const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1352 }
1353
1354 struct NadWildcard;
1355
1356 impl EdifactSegmentTag for NadWildcard {
1357 const SEGMENT_TAG: &'static str = "NAD";
1358 const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
1359 }
1360
1361 impl EdifactDeserialize for BgmSegment {
1362 fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
1363 let seg = find_segment(segments, "BGM").ok_or_else(|| {
1364 EdifactError::MissingRequiredElement {
1365 tag: "BGM".to_owned(),
1366 element_index: 0,
1367 }
1368 })?;
1369 Ok(Self {
1370 doc_name_code: element_str(seg, 0).to_owned(),
1371 pruef_id: element_str(seg, 1).to_owned(),
1372 msg_function: seg
1373 .element_str(2)
1374 .filter(|s| !s.is_empty())
1375 .map(str::to_owned),
1376 })
1377 }
1378 }
1379
1380 #[test]
1381 fn deserialize_single_segment() {
1382 let input = b"BGM+E03+11042+9'";
1383 let bgm: BgmSegment = deserialize(input).unwrap();
1384 assert_eq!(bgm.doc_name_code, "E03");
1385 assert_eq!(bgm.pruef_id, "11042");
1386 assert_eq!(bgm.msg_function, Some("9".to_owned()));
1387 }
1388
1389 #[test]
1390 fn streaming_deserialize_first_from_bytes() {
1391 let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
1392 let bgm: BgmSegment = deserialize_first_streaming(input).unwrap();
1393 assert_eq!(bgm.pruef_id, "11042");
1394 }
1395
1396 #[test]
1397 fn streaming_deserialize_all_from_bytes() {
1398 let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
1399 let bgms: Vec<BgmSegment> = deserialize_all_streaming(input).unwrap();
1400 assert_eq!(bgms.len(), 2);
1401 assert_eq!(bgms[0].pruef_id, "11042");
1402 assert_eq!(bgms[1].pruef_id, "11043");
1403 }
1404
1405 #[test]
1406 fn streaming_deserialize_first_from_reader() {
1407 let input =
1408 std::io::Cursor::new(b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'".to_vec());
1409 let bgm: BgmSegment = deserialize_first_from_reader(input).unwrap();
1410 assert_eq!(bgm.pruef_id, "11042");
1411 }
1412
1413 #[test]
1414 fn streaming_deserialize_all_from_reader() {
1415 let input = std::io::Cursor::new(b"BGM+E03+11042+9'BGM+E01+11043+9'".to_vec());
1416 let bgms: Vec<BgmSegment> = deserialize_all_from_reader(input).unwrap();
1417 assert_eq!(bgms.len(), 2);
1418 assert_eq!(bgms[0].pruef_id, "11042");
1419 assert_eq!(bgms[1].pruef_id, "11043");
1420 }
1421
1422 #[test]
1423 fn missing_segment_returns_error() {
1424 let input = b"DTM+137:20230401:102'";
1425 let result: Result<BgmSegment, _> = deserialize(input);
1426 assert!(result.is_err());
1427 }
1428
1429 #[test]
1430 fn vec_collects_all_matching_segments() {
1431 let input = b"DTM+137:20230401:102'BGM+E03+11042+9'BGM+E01+11043+9'";
1432 let bgms: Vec<BgmSegment> = deserialize(input).unwrap();
1433 assert_eq!(bgms.len(), 2);
1434 assert_eq!(bgms[0].pruef_id, "11042");
1435 assert_eq!(bgms[1].pruef_id, "11043");
1436 }
1437
1438 #[test]
1439 fn find_qualified_segment_matches_qualifier() {
1440 let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1441 let segments: Vec<Segment<'_>> =
1442 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1443 let nad_ms = find_qualified_segment(&segments, "NAD", "MS");
1444 let nad_mr = find_qualified_segment(&segments, "NAD", "MR");
1445 assert!(nad_ms.is_some());
1446 assert!(nad_mr.is_some());
1447 assert_eq!(element_str(nad_ms.unwrap(), 0), "MS");
1448 assert_eq!(element_str(nad_mr.unwrap(), 0), "MR");
1449 }
1450
1451 #[test]
1452 fn round_trip_str_api() {
1453 let input = "BGM+E03+11042+9'";
1454 let bgm: BgmSegment = deserialize_str(input).unwrap();
1455 assert_eq!(bgm.pruef_id, "11042");
1456 }
1457
1458 #[test]
1459 fn required_element_extraction() {
1460 let input = b"BGM+E03+11042+9'";
1461 let segments: Vec<Segment<'_>> =
1462 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1463 let seg = &segments[0];
1464
1465 assert_eq!(required_element(seg, 0).unwrap(), "E03");
1466 assert_eq!(required_element(seg, 1).unwrap(), "11042");
1467 assert!(required_element(seg, 5).is_err());
1469 }
1470
1471 #[test]
1472 fn optional_element_extraction() {
1473 let input = b"BGM+E03+11042+9'BGM+E01++absent'";
1474 let segments: Vec<Segment<'_>> =
1475 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1476
1477 assert_eq!(optional_element(&segments[0], 0), Some("E03"));
1479 assert_eq!(optional_element(&segments[0], 1), Some("11042"));
1480 assert_eq!(optional_element(&segments[0], 5), None);
1481
1482 assert_eq!(optional_element(&segments[1], 1), None);
1484 }
1485
1486 #[test]
1487 fn component_extraction() {
1488 let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1489 let segments: Vec<Segment<'_>> =
1490 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1491 let seg = &segments[0];
1492
1493 assert_eq!(required_component(seg, 0, 0).unwrap(), "UNOA");
1494 assert_eq!(required_component(seg, 0, 1).unwrap(), "1");
1495 assert!(required_component(seg, 0, 5).is_err());
1497 }
1498
1499 #[test]
1500 fn composite_element_helper() {
1501 let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1502 let segments: Vec<Segment<'_>> =
1503 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1504 let seg = &segments[0];
1505
1506 let comp = composite_element(seg, 0).unwrap();
1507 assert_eq!(comp.len(), 2);
1508 assert_eq!(comp.get(0), Some("UNOA"));
1509 assert_eq!(comp.get(1), Some("1"));
1510 assert_eq!(comp.get(5), None);
1511 assert_eq!(comp.get_or_empty(5), "");
1512 }
1513
1514 #[test]
1515 fn get_all_components() {
1516 let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1518 let segments: Vec<Segment<'_>> =
1519 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1520 let seg = &segments[0];
1521
1522 let comps: Vec<&str> = get_components_iter(seg, 0).collect(); assert!(!comps.is_empty(), "Expected components but got empty");
1524 assert_eq!(comps.len(), 2);
1525 assert_eq!(comps[0], "UNOA");
1526 assert_eq!(comps[1], "1");
1527 }
1528
1529 #[test]
1530 fn qualifier_pattern_matching_supports_exact_and_wildcard() {
1531 assert!(qualifier_matches_pattern("MS", "MS"));
1533 assert!(!qualifier_matches_pattern("MS", "M")); assert!(qualifier_matches_pattern("MS", "M*"));
1536 assert!(qualifier_matches_pattern("MRY", "M*Y"));
1537 assert!(!qualifier_matches_pattern("AB", "M*"));
1538 }
1539
1540 #[test]
1542 fn qualifier_matches_pattern_table() {
1543 let cases: &[(&str, &str, bool)] = &[
1545 ("", "", true), ("", "*", true), ("A", "", false), ("", "A", false), ("MS", "MS", true),
1552 ("BY", "BY", true),
1553 ("ms", "MS", false), ("MSX", "MS", false), ("M", "MS", false), ("MS", "M*", true),
1558 ("MULTI", "MUL*", true),
1559 ("AB", "M*", false),
1560 ("", "M*", false), ("MSG", "*G", true),
1563 ("G", "*G", true),
1564 ("MSG", "*X", false),
1565 ("", "*G", false),
1566 ("MRY", "M*Y", true),
1568 ("MAY", "M*Y", true),
1569 ("MY", "M*Y", true), ("MYY", "M*Y", true), ("MAYZ", "M*Y", false), ("AB", "M*Y", false),
1573 ("*", "*", true), ("anything", "*", true),
1576 ("", "*", true),
1577 ("ABCDE", "A*C*E", true),
1579 ("ACE", "A*C*E", true), ("AXCYE", "A*C*E", true),
1581 ("ABCDF", "A*C*E", false),
1582 ("AB", "A**B", true), ("AB", "A*B*C", false),
1586 ("XMS", "MS", false),
1588 ];
1589
1590 for (value, pattern, expected) in cases {
1591 let got = qualifier_matches_pattern(value, pattern);
1592 assert_eq!(
1593 got, *expected,
1594 "qualifier_matches_pattern({value:?}, {pattern:?}) expected {expected} but got {got}"
1595 );
1596 }
1597 }
1598
1599 #[test]
1600 fn typed_qualifier_helpers_work() {
1601 let input = b"NAD+MS+9900001+293'NAD+MR+9900002+293'";
1602 let segments: Vec<Segment<'_>> =
1603 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1604
1605 let first = find_segment_typed::<NadM>(&segments).unwrap();
1606 assert_eq!(first.element_str(0), Some("MS"));
1607
1608 let all: Vec<_> = find_segments_typed::<NadWildcard>(&segments).collect();
1609 assert_eq!(all.len(), 2);
1610 }
1611
1612 #[test]
1613 fn segment_accessor_trait_methods_work() {
1614 let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'";
1615 let segments: Vec<Segment<'_>> =
1616 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1617 let seg = &segments[0];
1618
1619 assert_eq!(SegmentAccessor::get_element(seg, 1), Some("SENDER"));
1620 assert_eq!(SegmentAccessor::required_composite(seg, 0, 1).unwrap(), "1");
1621 let parsed: i32 = SegmentAccessor::code_element(seg, 4).unwrap();
1622 assert_eq!(parsed, 1);
1623 let reps = SegmentAccessor::repeating_components(seg, 3, 0, 2).unwrap();
1624 assert_eq!(reps, vec!["200101", "0900"]);
1625 }
1626
1627 #[test]
1628 fn group_helpers_detect_contiguity() {
1629 struct NadAny;
1630 impl EdifactSegmentTag for NadAny {
1631 const SEGMENT_TAG: &'static str = "NAD";
1632 }
1633
1634 let contiguous_input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'";
1635 let contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(contiguous_input)
1636 .collect::<Result<_, _>>()
1637 .unwrap();
1638 assert!(groups_are_contiguous_by_qualifier::<NadAny>(
1639 &contiguous_segments
1640 ));
1641
1642 let non_contiguous_input = b"NAD+MS+1'RFF+AA:1'NAD+MR+2'";
1643 let non_contiguous_segments: Vec<Segment<'_>> = crate::from_bytes(non_contiguous_input)
1644 .collect::<Result<_, _>>()
1645 .unwrap();
1646 assert!(!groups_are_contiguous_by_qualifier::<NadAny>(
1647 &non_contiguous_segments
1648 ));
1649 }
1650
1651 #[test]
1652 fn group_helpers_collect_contiguous_groups() {
1653 struct NadAny;
1654 impl EdifactSegmentTag for NadAny {
1655 const SEGMENT_TAG: &'static str = "NAD";
1656 }
1657
1658 let input = b"NAD+MS+1'NAD+MR+2'RFF+AA:1'NAD+BY+3'";
1659 let segments: Vec<Segment<'_>> =
1660 crate::from_bytes(input).collect::<Result<_, _>>().unwrap();
1661 let groups = contiguous_groups_by_qualifier::<NadAny>(&segments);
1662
1663 assert_eq!(groups.len(), 2);
1664 assert_eq!(groups[0].len(), 2);
1665 assert_eq!(groups[1].len(), 1);
1666 }
1667
1668 #[test]
1671 fn message_windows_bytes_yields_complete_windows() {
1672 let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1673 UNH+1+ORDERS:D:96A:UN'\
1674 BGM+220+PO-001+9'\
1675 UNT+3+1'\
1676 UNZ+1+1'";
1677 let windows: Vec<_> = message_windows_bytes(input)
1678 .collect::<Result<_, _>>()
1679 .unwrap();
1680 assert_eq!(windows.len(), 1);
1681 assert_eq!(windows[0].segments[0].tag, "UNH");
1682 assert_eq!(windows[0].segments.last().unwrap().tag, "UNT");
1683 assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1684 assert_eq!(windows[0].association_code.as_deref(), None);
1685 }
1686
1687 #[test]
1688 fn message_windows_bytes_preserves_owned_unh_metadata() {
1689 let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
1690 UNH+1+ORD?ERS:D:96A:UN:5??5??3a'\
1691 BGM+220+PO-001+9'\
1692 UNT+3+1'\
1693 UNZ+1+1'";
1694 let windows: Vec<_> = message_windows_bytes(input)
1695 .collect::<Result<_, _>>()
1696 .unwrap();
1697
1698 assert_eq!(windows.len(), 1);
1699 assert_eq!(windows[0].message_type.as_deref(), Some("ORDERS"));
1700 assert_eq!(windows[0].association_code.as_deref(), Some("5?5?3a"));
1701 }
1702
1703 #[test]
1704 fn message_windows_truncated_stream_returns_error() {
1705 let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1707 let results: Vec<_> = message_windows_bytes(input).collect();
1708 assert_eq!(results.len(), 1);
1709 assert!(
1710 matches!(results[0], Err(EdifactError::UnexpectedEof { .. })),
1711 "expected UnexpectedEof for truncated window, got: {:?}",
1712 results[0]
1713 );
1714 }
1715
1716 #[test]
1717 fn message_windows_subsequent_calls_return_none_after_truncation() {
1718 let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'";
1719 let mut iter = message_windows_bytes(input);
1720 assert!(matches!(
1721 iter.next(),
1722 Some(Err(EdifactError::UnexpectedEof { .. }))
1723 ));
1724 assert!(iter.next().is_none());
1726 }
1727
1728 #[test]
1729 fn message_windows_unh_without_unt_before_next_unh_returns_error() {
1730 let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+PO-001+9'\
1731 UNH+2+ORDERS:D:96A:UN'BGM+220+PO-002+9'UNT+3+2'";
1732 let results: Vec<_> = message_windows_bytes(input).collect();
1733 assert!(
1735 matches!(
1736 results[0],
1737 Err(EdifactError::InvalidSegmentForMessage { ref tag, .. }) if tag == "UNH"
1738 ),
1739 "expected InvalidSegmentForMessage(UNH), got: {:?}",
1740 results[0]
1741 );
1742 }
1743
1744 fn parse_one(input: &str) -> crate::OwnedSegment {
1747 crate::from_reader(std::io::Cursor::new(input.as_bytes()))
1748 .expect("parse failed")
1749 .into_iter()
1750 .next()
1751 .expect("at least one segment")
1752 }
1753
1754 #[test]
1755 fn segment_accessor_get_element_returns_value() {
1756 let owned = parse_one("BGM+220+PO-001+9'");
1757 let seg = owned.as_borrowed();
1758 assert_eq!(SegmentAccessor::get_element(&seg, 0), Some("220"));
1759 assert_eq!(SegmentAccessor::get_element(&seg, 1), Some("PO-001"));
1760 assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("9"));
1761 assert_eq!(
1762 SegmentAccessor::get_element(&seg, 9),
1763 None,
1764 "out-of-bounds must return None"
1765 );
1766 }
1767
1768 #[test]
1769 fn segment_accessor_get_element_filters_empty() {
1770 let owned = parse_one("TST+++VALUE'");
1771 let seg = owned.as_borrowed();
1772 assert_eq!(
1774 SegmentAccessor::get_element(&seg, 0),
1775 None,
1776 "empty element must return None"
1777 );
1778 assert_eq!(
1779 SegmentAccessor::get_element(&seg, 1),
1780 None,
1781 "empty element must return None"
1782 );
1783 assert_eq!(SegmentAccessor::get_element(&seg, 2), Some("VALUE"));
1784 }
1785
1786 #[test]
1787 fn segment_accessor_get_component_returns_value() {
1788 let owned = parse_one("UNH+1+ORDERS:D:96A:UN'");
1789 let seg = owned.as_borrowed();
1790 assert_eq!(seg.get_component(1, 0), Some("ORDERS"));
1791 assert_eq!(seg.get_component(1, 1), Some("D"));
1792 assert_eq!(seg.get_component(1, 2), Some("96A"));
1793 assert_eq!(seg.get_component(1, 3), Some("UN"));
1794 assert_eq!(
1795 seg.get_component(1, 9),
1796 None,
1797 "out-of-bounds must return None"
1798 );
1799 }
1800
1801 #[test]
1802 fn segment_accessor_text_element_errors_on_missing() {
1803 let owned = parse_one("BGM+'");
1804 let seg = owned.as_borrowed();
1805 let err = seg.text_element(0);
1807 assert!(
1808 matches!(err, Err(EdifactError::MissingRequiredElement { ref tag, element_index: 0 }) if tag == "BGM"),
1809 "expected MissingRequiredElement, got: {err:?}"
1810 );
1811 }
1812
1813 #[test]
1814 fn segment_accessor_required_composite_errors_on_missing() {
1815 let owned = parse_one("DTM+137'");
1816 let seg = owned.as_borrowed();
1817 let err = seg.required_composite(0, 1);
1819 assert!(
1820 matches!(err, Err(EdifactError::MissingRequiredComponent { ref tag, element_index: 0, component_index: 1 }) if tag == "DTM"),
1821 "expected MissingRequiredComponent, got: {err:?}"
1822 );
1823 }
1824
1825 #[test]
1826 fn segment_accessor_code_element_parses_integer() {
1827 let owned = parse_one("QTY+21:100'");
1828 let seg = owned.as_borrowed();
1829 let qty: u32 = seg.code_element(0).expect("should parse qualifier as u32");
1830 assert_eq!(qty, 21);
1831 }
1832
1833 #[test]
1834 fn segment_accessor_optional_element_absent_returns_none() {
1835 let owned = parse_one("BGM+220'");
1836 let seg = owned.as_borrowed();
1837 assert_eq!(seg.optional_element(5), None);
1838 }
1839}