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