1use crate::{
4 error::EdifactError,
5 model::{Components, Element, OwnedSegment, Segment, Span},
6 tokenizer::{Token, Tokenizer},
7};
8use memchr::memchr2;
9use smallvec::SmallVec;
10use std::borrow::Cow;
11use std::io::{BufRead, BufReader, Read};
12
13struct PendingElement<'a> {
16 start: Option<usize>,
17 components: Components<'a>,
18 repeats: Vec<Components<'a>>,
19}
20
21impl<'a> PendingElement<'a> {
22 fn new() -> Self {
23 Self {
24 start: None,
25 components: SmallVec::new(),
26 repeats: Vec::new(),
27 }
28 }
29
30 #[inline]
31 fn is_open(&self) -> bool {
32 self.start.is_some()
33 }
34
35 #[inline]
37 fn open(&mut self, start: usize) {
38 if self.start.is_none() {
39 self.start = Some(start);
40 }
41 }
42
43 #[inline]
45 fn end_repetition(&mut self) {
46 self.repeats.push(std::mem::take(&mut self.components));
47 }
48
49 fn flush(&mut self, elements: &mut Vec<Element<'a>>) -> Option<usize> {
51 let start = self.start.take()?;
52 let mut repeats = std::mem::take(&mut self.repeats);
53 repeats.push(std::mem::take(&mut self.components));
54 let end = repeats
58 .iter()
59 .rev()
60 .find_map(|rep| rep.last().map(|(_, span)| span.end))?;
61 let components = repeats.remove(0);
62 elements.push(Element {
63 span: Span::new(start, end),
64 components,
65 repeats,
66 });
67 Some(end)
68 }
69}
70
71fn resolve_release(
72 val: &str,
73 release_char: char,
74 start_offset: usize,
75) -> Result<Cow<'_, str>, EdifactError> {
76 if !val.contains(release_char) {
77 return Ok(Cow::Borrowed(val));
78 }
79 resolve_release_owned(val, release_char, start_offset).map(Cow::Owned)
80}
81
82fn resolve_release_owned(
83 val: &str,
84 release_char: char,
85 start_offset: usize,
86) -> Result<String, EdifactError> {
87 let cap = val.len() - val.len() / 4;
93 let mut out = String::with_capacity(cap);
94 let mut chars = val.chars();
95 while let Some(c) = chars.next() {
96 if c == release_char {
97 if let Some(escaped) = chars.next() {
98 out.push(escaped);
99 } else {
100 return Err(EdifactError::InvalidReleaseSequence {
101 offset: start_offset + val.len().saturating_sub(1),
102 });
103 }
104 } else {
105 out.push(c);
106 }
107 }
108 Ok(out)
109}
110
111pub struct Parser<'a> {
116 tokenizer: Tokenizer<'a>,
117 peeked: Option<Token<'a>>,
119 release_char: char,
121}
122
123impl<'a> Parser<'a> {
124 pub fn new(tokenizer: Tokenizer<'a>) -> Self {
126 let release_char = tokenizer.service_string_advice().release_char as char;
127 Self {
128 tokenizer,
129 peeked: None,
130 release_char,
131 }
132 }
133}
134
135impl<'a> Iterator for Parser<'a> {
136 type Item = Result<Segment<'a>, EdifactError>;
137
138 fn next(&mut self) -> Option<Self::Item> {
139 let (tag, tag_span) = loop {
141 let tok = match self.peeked.take() {
142 Some(t) => Ok(t),
143 None => self.tokenizer.next()?,
144 };
145 match tok {
146 Ok(Token::SegmentTag { value, span }) => break (value, span),
147 Ok(Token::SegmentTerminator { .. }) => continue, Ok(Token::DataElement { span, .. })
149 | Ok(Token::ComponentElement { span, .. })
150 | Ok(Token::RepeatElement { span, .. }) => {
151 return Some(Err(EdifactError::UnexpectedDataToken {
152 offset: span.start,
153 }));
154 }
155 Err(e) => return Some(Err(e)),
156 }
157 };
158
159 let mut elements: Vec<Element<'a>> = Vec::new();
164 let mut pending = PendingElement::new();
165 let mut segment_end = tag_span.end;
166
167 loop {
168 let tok = match self.tokenizer.next() {
169 Some(Ok(t)) => t,
170 Some(Err(e)) => return Some(Err(e)),
171 None => {
172 if let Some(end) = pending.flush(&mut elements) {
178 segment_end = end;
179 }
180 return Some(Err(EdifactError::UnexpectedEof {
181 offset: segment_end,
182 }));
183 }
184 };
185
186 match tok {
187 Token::SegmentTag {
188 value: next_tag,
189 span,
190 } => {
191 self.peeked = Some(Token::SegmentTag {
199 value: next_tag,
200 span,
201 });
202 if let Some(end) = pending.flush(&mut elements) {
203 segment_end = end;
204 }
205 return Some(Err(EdifactError::UnexpectedEof {
206 offset: segment_end,
207 }));
208 }
209 Token::SegmentTerminator { span } => {
210 pending.flush(&mut elements);
211 segment_end = span.end;
212 break;
213 }
214 Token::DataElement { value, span } => {
215 pending.flush(&mut elements);
216 let resolved = match resolve_release(value, self.release_char, span.start) {
217 Ok(v) => v,
218 Err(error) => return Some(Err(error)),
219 };
220 pending.open(span.start);
221 pending.components.push((resolved, span));
222 }
223 Token::ComponentElement { value, span } => {
224 let resolved = match resolve_release(value, self.release_char, span.start) {
226 Ok(v) => v,
227 Err(error) => return Some(Err(error)),
228 };
229 pending.open(span.start);
230 pending.components.push((resolved, span));
231 }
232 Token::RepeatElement { value, span } => {
233 let resolved = match resolve_release(value, self.release_char, span.start) {
234 Ok(v) => v,
235 Err(error) => return Some(Err(error)),
236 };
237 if !pending.is_open() {
240 return Some(Err(EdifactError::UnexpectedDataToken {
241 offset: span.start,
242 }));
243 }
244 pending.end_repetition();
245 pending.components.push((resolved, span));
246 }
247 }
248 }
249
250 Some(Ok(Segment {
251 tag: Cow::Borrowed(tag),
252 span: Span::new(tag_span.start, segment_end),
253 tag_span,
254 elements,
255 }))
256 }
257}
258
259#[derive(Debug, Clone, Copy)]
275pub struct ReaderConfig {
276 pub max_segment_bytes: usize,
286 pub max_segments: Option<usize>,
293 pub max_input_bytes: Option<u64>,
302 pub max_messages: Option<usize>,
309 pub service_string_advice: Option<crate::tokenizer::ServiceStringAdvice>,
322}
323
324impl Default for ReaderConfig {
325 fn default() -> Self {
326 Self {
327 max_segment_bytes: 65_536,
328 max_segments: None,
329 max_input_bytes: None,
330 max_messages: None,
331 service_string_advice: None,
332 }
333 }
334}
335
336impl ReaderConfig {
337 #[must_use]
339 pub fn max_segment_bytes(mut self, limit: usize) -> Self {
340 self.max_segment_bytes = limit;
341 self
342 }
343
344 #[must_use]
346 pub fn max_segments(mut self, limit: usize) -> Self {
347 self.max_segments = Some(limit);
348 self
349 }
350
351 #[must_use]
353 pub fn max_input_bytes(mut self, limit: u64) -> Self {
354 self.max_input_bytes = Some(limit);
355 self
356 }
357
358 #[must_use]
360 pub fn max_messages(mut self, limit: usize) -> Self {
361 self.max_messages = Some(limit);
362 self
363 }
364
365 #[must_use]
384 pub fn with_service_string_advice(
385 mut self,
386 ssa: crate::tokenizer::ServiceStringAdvice,
387 ) -> Self {
388 self.service_string_advice = Some(ssa);
389 self
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395enum StreamState {
396 Init,
398 Running,
400 Done,
402}
403
404pub struct OwnedSegmentStream<R: BufRead> {
422 reader: R,
423 ssa: crate::tokenizer::ServiceStringAdvice,
424 state: StreamState,
425 stream_offset: u64,
426 config: ReaderConfig,
427 segments_yielded: usize,
429 messages_yielded: usize,
431 in_message: bool,
433 bytes_consumed: u64,
435 delimiters_settled: bool,
439}
440
441impl<R: BufRead> OwnedSegmentStream<R> {
442 fn new(reader: R) -> Self {
443 Self::with_config(reader, ReaderConfig::default())
444 }
445
446 fn with_config(reader: R, config: ReaderConfig) -> Self {
447 let (ssa, delimiters_settled) = match config.service_string_advice {
448 Some(ssa) => (ssa, true),
449 None => (crate::tokenizer::ServiceStringAdvice::default(), false),
450 };
451 Self {
452 reader,
453 ssa,
454 state: StreamState::Init,
455 stream_offset: 0,
456 config,
457 segments_yielded: 0,
458 messages_yielded: 0,
459 in_message: false,
460 bytes_consumed: 0,
461 delimiters_settled,
462 }
463 }
464
465 fn adopt_syntax_version(&mut self, unb: &OwnedSegment) {
475 self.delimiters_settled = true;
476 if unb.component_str(0, 1) == Some("4") {
477 self.ssa.repetition_sep = b'*';
478 }
479 }
480
481 fn check_limits(&self, tag: &str) -> Option<EdifactError> {
487 if let Some(max) = self.config.max_segments {
488 if self.segments_yielded >= max {
489 return Some(EdifactError::LimitExceeded {
490 limit: "max_segments",
491 max: max as u64,
492 });
493 }
494 }
495 if let Some(max) = self.config.max_messages {
499 if tag == "UNH" && self.messages_yielded >= max {
500 return Some(EdifactError::LimitExceeded {
501 limit: "max_messages",
502 max: max as u64,
503 });
504 }
505 }
506 if let Some(max) = self.config.max_input_bytes {
507 if self.bytes_consumed > max {
508 return Some(EdifactError::LimitExceeded {
509 limit: "max_input_bytes",
510 max,
511 });
512 }
513 }
514 None
515 }
516
517 fn account(&mut self, tag: &str) {
522 self.segments_yielded += 1;
523 if tag == "UNT" {
524 if self.in_message {
525 self.messages_yielded += 1;
526 }
527 self.in_message = false;
528 } else if tag == "UNH" {
529 self.in_message = true;
530 }
531 }
532}
533
534enum FastSegment {
538 Parsed(OwnedSegment, usize),
540 Skip(usize),
542 NeedMore,
544 Eof,
546 Err(EdifactError),
548}
549
550fn find_unescaped_term(buf: &[u8], term: u8, release: u8) -> Option<usize> {
562 let mut i = 0;
563 while i < buf.len() {
564 let rel = memchr2(release, term, &buf[i..])?;
566 let pos = i + rel;
567 if buf[pos] == release {
568 i = pos + 2;
570 } else {
571 return Some(pos);
573 }
574 }
575 None
576}
577
578fn try_fast_segment<R: BufRead>(
583 reader: &mut R,
584 ssa: crate::tokenizer::ServiceStringAdvice,
585 seg_start: usize,
586 max_segment_bytes: usize,
587) -> FastSegment {
588 let buf = match reader.fill_buf() {
589 Ok(b) => b,
590 Err(e) => return FastSegment::Err(e.into()),
591 };
592
593 if buf.is_empty() {
594 return FastSegment::Eof;
595 }
596
597 let Some(pos) = find_unescaped_term(buf, ssa.segment_term, ssa.release_char) else {
598 return FastSegment::NeedMore;
599 };
600
601 if pos > max_segment_bytes {
604 return FastSegment::Err(EdifactError::SegmentTooLong {
605 offset: seg_start,
606 limit: max_segment_bytes,
607 });
608 }
609
610 let seg_bytes = &buf[..pos];
612
613 if seg_bytes
615 .iter()
616 .all(|&b| matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
617 {
618 return FastSegment::Skip(pos + 1);
619 }
620
621 let tok = Tokenizer::for_segment(&buf[..pos + 1], ssa, max_segment_bytes);
630 let mut parser_iter = Parser::new(tok);
631 match parser_iter.next() {
632 None => FastSegment::Skip(pos + 1),
633 Some(Err(e)) => FastSegment::Err(e),
634 Some(Ok(s)) => FastSegment::Parsed(s.into_owned().offset(seg_start), pos + 1),
635 }
636 }
638
639impl<R: BufRead> Iterator for OwnedSegmentStream<R> {
642 type Item = Result<OwnedSegment, EdifactError>;
643
644 fn next(&mut self) -> Option<Self::Item> {
645 if self.state == StreamState::Done {
646 return None;
647 }
648
649 loop {
650 if self.state == StreamState::Running {
652 let seg_start = self.stream_offset;
653 match try_fast_segment(
654 &mut self.reader,
655 self.ssa,
656 seg_start.min(usize::MAX as u64) as usize,
660 self.config.max_segment_bytes,
661 ) {
662 FastSegment::Parsed(seg, n) => {
663 let n = n as u64;
664 self.reader.consume(n as usize);
665 self.stream_offset += n;
666 self.bytes_consumed = self.stream_offset;
667 if let Some(error) = self.check_limits(&seg.tag) {
668 self.state = StreamState::Done;
669 return Some(Err(error));
670 }
671 if !self.delimiters_settled && seg.tag == "UNB" {
672 self.adopt_syntax_version(&seg);
673 }
674 self.account(&seg.tag);
675 return Some(Ok(seg));
676 }
677 FastSegment::Skip(n) => {
678 let n = n as u64;
679 self.reader.consume(n as usize);
680 self.stream_offset += n;
681 self.bytes_consumed = self.stream_offset;
682 continue;
683 }
684 FastSegment::Eof => return None,
685 FastSegment::Err(e) => {
686 self.state = StreamState::Done;
687 return Some(Err(e));
688 }
689 FastSegment::NeedMore => {
690 }
692 }
693 }
694
695 if self.state == StreamState::Init {
697 match self.reader.fill_buf() {
702 Ok(buf) if buf.starts_with(&crate::tokenizer::UTF8_BOM) => {
703 self.reader.consume(3);
704 self.stream_offset += 3;
705 self.bytes_consumed = self.stream_offset;
706 }
707 Ok(_) => {}
708 Err(error) => {
709 self.state = StreamState::Done;
710 return Some(Err(error.into()));
711 }
712 }
713 }
714 let mut scanned = self.state != StreamState::Init;
715 let mut slow_offset: usize = self.stream_offset.min(usize::MAX as u64) as usize;
720 let mut raw = match read_next_raw_segment(
721 &mut self.reader,
722 &mut self.ssa,
723 &mut scanned,
724 &mut slow_offset,
725 self.config.max_segment_bytes,
726 &mut self.delimiters_settled,
727 self.config.service_string_advice.is_some(),
728 ) {
729 Ok(Some(r)) => r,
730 Ok(None) => return None,
731 Err(e) => {
732 self.state = StreamState::Done;
733 return Some(Err(e));
734 }
735 };
736 self.stream_offset = slow_offset as u64;
737 if scanned {
738 self.state = StreamState::Running;
739 }
740 self.bytes_consumed = self.stream_offset;
741
742 raw.bytes.push(self.ssa.segment_term);
743 let tok = Tokenizer::for_segment(
747 raw.bytes.as_slice(),
748 self.ssa,
749 self.config.max_segment_bytes,
750 );
751 let mut parser_iter = Parser::new(tok);
752 match parser_iter.next() {
753 Some(Ok(s)) => {
754 let seg = s.into_owned().offset(raw.start_offset);
755 if let Some(error) = self.check_limits(&seg.tag) {
756 self.state = StreamState::Done;
757 return Some(Err(error));
758 }
759 if !self.delimiters_settled && seg.tag == "UNB" {
760 self.adopt_syntax_version(&seg);
761 }
762 self.account(&seg.tag);
763 return Some(Ok(seg));
764 }
765 Some(Err(e)) => {
766 self.state = StreamState::Done;
767 return Some(Err(e));
768 }
769 None => {} }
771 }
772 }
773}
774
775pub fn from_bufread<R: BufRead>(reader: R) -> OwnedSegmentStream<R> {
785 OwnedSegmentStream::new(reader)
786}
787
788pub fn from_bufread_with_config<R: BufRead>(
790 reader: R,
791 config: ReaderConfig,
792) -> OwnedSegmentStream<R> {
793 OwnedSegmentStream::with_config(reader, config)
794}
795
796pub fn from_reader_stream<R: Read>(reader: R) -> OwnedSegmentStream<BufReader<R>> {
798 from_bufread(BufReader::new(reader))
799}
800
801pub fn from_reader_with_config<R: Read>(
814 reader: R,
815 config: ReaderConfig,
816) -> OwnedSegmentStream<BufReader<R>> {
817 from_bufread_with_config(BufReader::new(reader), config)
818}
819
820#[allow(clippy::too_many_arguments)]
821fn read_next_raw_segment<R: BufRead>(
822 reader: &mut R,
823 ssa: &mut crate::tokenizer::ServiceStringAdvice,
824 scanned_header: &mut bool,
825 stream_offset: &mut usize,
826 max_segment_bytes: usize,
827 delimiters_settled: &mut bool,
828 advice_overridden: bool,
829) -> Result<Option<crate::tokenizer::RawSegment>, EdifactError> {
830 loop {
831 let Some((first_offset, first)) = read_next_non_ws_byte(reader, stream_offset)? else {
832 return Ok(None);
833 };
834
835 if !*scanned_header && first == b'U' {
836 let second = read_required_byte(reader, stream_offset)?;
837 let third = read_required_byte(reader, stream_offset)?;
838 if second == b'N' && third == b'A' {
839 let mut una = [0u8; 9];
840 una[0] = b'U';
841 una[1] = b'N';
842 una[2] = b'A';
843 for slot in una.iter_mut().skip(3) {
844 *slot = read_required_byte(reader, stream_offset)?;
845 }
846 let declared = crate::tokenizer::ServiceStringAdvice {
847 component_sep: una[3],
848 element_sep: una[4],
849 decimal_mark: una[5],
850 release_char: una[6],
851 repetition_sep: una[7],
852 segment_term: una[8],
853 };
854 if !declared.is_valid() {
855 return Err(EdifactError::InvalidUna);
856 }
857 if !advice_overridden {
861 *ssa = declared;
862 }
863 *delimiters_settled = true;
864 *scanned_header = true;
865 continue;
866 }
867
868 *scanned_header = true;
869 return read_remainder_of_segment(
870 reader,
871 ssa,
872 crate::tokenizer::RawSegment {
873 bytes: vec![first, second, third],
874 start_offset: first_offset,
875 },
876 stream_offset,
877 max_segment_bytes,
878 );
879 }
880
881 *scanned_header = true;
882 return read_remainder_of_segment(
883 reader,
884 ssa,
885 crate::tokenizer::RawSegment {
886 bytes: vec![first],
887 start_offset: first_offset,
888 },
889 stream_offset,
890 max_segment_bytes,
891 );
892 }
893}
894
895fn read_remainder_of_segment<R: BufRead>(
896 reader: &mut R,
897 ssa: &crate::tokenizer::ServiceStringAdvice,
898 mut out: crate::tokenizer::RawSegment,
899 stream_offset: &mut usize,
900 max_segment_bytes: usize,
901) -> Result<Option<crate::tokenizer::RawSegment>, EdifactError> {
902 let mut escaped = false;
903 loop {
904 if out.bytes.len() > max_segment_bytes {
910 return Err(EdifactError::SegmentTooLong {
911 offset: out.start_offset,
912 limit: max_segment_bytes,
913 });
914 }
915 let Some(byte) = read_next_byte(reader, stream_offset)? else {
916 return if out.bytes.is_empty() {
917 Ok(None)
918 } else if escaped {
919 Err(EdifactError::InvalidReleaseSequence {
920 offset: out.start_offset + out.bytes.len().saturating_sub(1),
921 })
922 } else {
923 Err(EdifactError::UnexpectedEof {
924 offset: out.start_offset + out.bytes.len(),
925 })
926 };
927 };
928
929 if !escaped && byte == ssa.segment_term {
930 return Ok(Some(out));
931 }
932
933 if !escaped && byte == ssa.release_char {
934 escaped = true;
935 out.bytes.push(byte);
936 continue;
937 }
938
939 escaped = false;
940 out.bytes.push(byte);
941 }
942}
943
944fn read_next_byte<R: BufRead>(
945 reader: &mut R,
946 stream_offset: &mut usize,
947) -> Result<Option<u8>, EdifactError> {
948 let buf = reader.fill_buf()?;
949 if buf.is_empty() {
950 return Ok(None);
951 }
952
953 let byte = buf[0];
954 reader.consume(1);
955 let next_offset = stream_offset.saturating_add(1);
960 *stream_offset = next_offset;
961 Ok(Some(byte))
962}
963
964fn read_required_byte<R: BufRead>(
965 reader: &mut R,
966 stream_offset: &mut usize,
967) -> Result<u8, EdifactError> {
968 read_next_byte(reader, stream_offset)?.ok_or(EdifactError::UnexpectedEof {
969 offset: *stream_offset,
970 })
971}
972
973fn read_next_non_ws_byte<R: BufRead>(
974 reader: &mut R,
975 stream_offset: &mut usize,
976) -> Result<Option<(usize, u8)>, EdifactError> {
977 loop {
978 let current_offset = *stream_offset;
979 let Some(byte) = read_next_byte(reader, stream_offset)? else {
980 return Ok(None);
981 };
982 if !matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
983 return Ok(Some((current_offset, byte)));
984 }
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991 use crate::tokenizer::ServiceStringAdvice;
992
993 fn parse_all(input: &[u8]) -> Vec<Segment<'_>> {
994 let ssa = ServiceStringAdvice::from_bytes_unchecked(input);
995 let tok = Tokenizer::new(input, ssa);
996 Parser::new(tok)
997 .collect::<Result<Vec<_>, _>>()
998 .expect("parse failed")
999 }
1000
1001 #[test]
1002 fn parses_unb_unz() {
1003 let input = b"UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'UNZ+0+1'";
1004 let segs = parse_all(input);
1005 assert_eq!(segs.len(), 2);
1006 assert_eq!(segs[0].tag, "UNB");
1007 assert_eq!(segs[1].tag, "UNZ");
1008 assert_eq!(segs[0].tag_span, Span::new(0, 3));
1009 assert_eq!(segs[0].span, Span::new(0, 41));
1010 }
1011
1012 #[test]
1013 fn element_access() {
1014 let input = b"BGM+220+ORDER123+9'";
1015 let segs = parse_all(input);
1016 assert_eq!(segs[0].element_str(0), Some("220"));
1017 assert_eq!(segs[0].element_str(1), Some("ORDER123"));
1018 }
1019
1020 #[test]
1021 fn component_access() {
1022 let input = b"DTM+137:20200101:102'";
1023 let segs = parse_all(input);
1024 let dtm = &segs[0];
1025 assert_eq!(dtm.get_element(0).unwrap().get_component(0), Some("137"));
1026 assert_eq!(
1027 dtm.get_element(0).unwrap().get_component(1),
1028 Some("20200101")
1029 );
1030 assert_eq!(dtm.get_element(0).unwrap().get_component(2), Some("102"));
1031 }
1032
1033 #[test]
1034 fn release_char_resolved() {
1035 let input = b"FTX+AAA++test?+value'";
1036 let segs = parse_all(input);
1037 assert_eq!(segs[0].element_str(2), Some("test+value"));
1038 assert_eq!(
1039 segs[0].get_element(2).unwrap().component_span(0),
1040 Some(Span::new(9, 20))
1041 );
1042 }
1043
1044 #[test]
1045 fn reader_path_preserves_custom_una_delimiters() {
1046 let input = b"UNA:;.? 'BGM;220;test?;value'";
1047 let segments: Vec<_> =
1048 super::from_bufread(std::io::BufReader::new(std::io::Cursor::new(input)))
1049 .collect::<Result<_, _>>()
1050 .expect("reader parse should succeed");
1051 let bgm = segments
1052 .iter()
1053 .find(|segment| segment.tag == "BGM")
1054 .expect("BGM segment should be present");
1055 assert_eq!(bgm.elements[0].components[0].0, "220");
1056 assert_eq!(bgm.elements[1].components[0].0, "test;value");
1057 }
1058
1059 #[test]
1060 fn arbitrary_bytes_no_panic() {
1061 let garbage: &[u8] = b"\xff\x00\x01\x02ABC+++'''???";
1063 let _ = crate::from_bytes(garbage).collect::<Vec<_>>();
1064 }
1065
1066 #[test]
1067 fn from_reader_handles_chunk_boundaries() {
1068 let input = b"UNA:+.? 'BGM+220+test?+value'UNT+2+1'";
1069 let reader = std::io::BufReader::with_capacity(5, std::io::Cursor::new(input));
1070 let parsed = from_bufread(reader)
1071 .collect::<Result<Vec<_>, _>>()
1072 .expect("reader parsing should succeed");
1073 assert_eq!(parsed.len(), 2);
1074 assert_eq!(parsed[0].tag, "BGM");
1075 assert_eq!(parsed[0].elements[1].components[0].0, "test+value");
1076 assert_eq!(parsed[1].tag, "UNT");
1077 }
1078
1079 #[test]
1080 fn from_reader_without_una_uses_default_delimiters() {
1081 let input = b"BGM+220+X'UNT+2+1'";
1082 let parsed = crate::from_reader(std::io::Cursor::new(input))
1083 .collect::<Result<Vec<_>, _>>()
1084 .expect("reader parsing should succeed");
1085 assert_eq!(parsed.len(), 2);
1086 assert_eq!(parsed[0].tag, "BGM");
1087 assert_eq!(parsed[0].elements[0].components[0].0, "220");
1088 assert_eq!(parsed[1].span, Span::new(10, 18));
1089 }
1090
1091 #[test]
1092 fn dangling_release_sequence_is_error() {
1093 let input = b"FTX+AAA++dangling?";
1094 let err = crate::from_bytes(input)
1095 .collect::<Result<Vec<_>, _>>()
1096 .expect_err("expected dangling release to fail");
1097
1098 assert!(matches!(err, EdifactError::InvalidReleaseSequence { .. }));
1099 }
1100
1101 #[test]
1102 fn from_reader_reports_dangling_release_sequence() {
1103 let input = b"FTX+AAA++dangling?";
1104 let err = crate::from_reader(std::io::Cursor::new(input))
1105 .collect::<Result<Vec<_>, _>>()
1106 .expect_err("expected dangling release from reader path");
1107 assert!(matches!(err, EdifactError::InvalidReleaseSequence { .. }));
1108 }
1109
1110 #[test]
1111 fn a_segment_tagged_una_parses_the_same_on_both_paths() {
1112 let input = b"BGM+220'UNA+XXXXXX'BGM+221'";
1119
1120 let from_slice: Vec<_> = crate::from_bytes(input)
1121 .collect::<Result<Vec<_>, _>>()
1122 .expect("slice path");
1123 let from_reader = crate::from_reader(std::io::Cursor::new(&input[..]))
1124 .collect::<Result<Vec<_>, _>>()
1125 .expect("reader path must agree");
1126
1127 assert_eq!(
1128 from_slice.iter().map(|s| s.tag()).collect::<Vec<_>>(),
1129 from_reader.iter().map(|s| s.tag()).collect::<Vec<_>>(),
1130 );
1131 assert_eq!(from_reader[1].element_str(0), Some("XXXXXX"));
1132 }
1133
1134 #[test]
1135 fn the_reader_adopts_the_repetition_separator_from_the_unb_syntax_version() {
1136 let input = b"UNB+UNOC:4+S+R+260101:0900+IC1'RFF+ON:1*ON:2'UNZ+0+IC1'";
1139
1140 let from_slice: Vec<_> = crate::from_bytes(input)
1141 .collect::<Result<Vec<_>, _>>()
1142 .expect("slice path");
1143 let from_reader = crate::from_reader(std::io::Cursor::new(&input[..]))
1144 .collect::<Result<Vec<_>, _>>()
1145 .expect("reader path");
1146
1147 assert_eq!(from_slice[1].get_element(0).unwrap().repeat_count(), 2);
1148 assert_eq!(from_reader[1].elements[0].repeat_count(), 2);
1149 }
1150
1151 #[test]
1152 fn an_explicit_service_string_advice_parses_a_fragment_with_no_header() {
1153 let ssa = ServiceStringAdvice::from_bytes(b"UNA:;.? ~").expect("UNA");
1156 let config = ReaderConfig::default().with_service_string_advice(ssa);
1157
1158 let from_slice: Vec<_> = crate::from_bytes_with_config(b"BGM;220;PO-4711~", config)
1159 .collect::<Result<Vec<_>, _>>()
1160 .expect("slice path");
1161 assert_eq!(from_slice[0].element_str(1), Some("PO-4711"));
1162
1163 let from_reader: Vec<_> =
1164 from_reader_with_config(std::io::Cursor::new(b"BGM;220;PO-4711~"), config)
1165 .collect::<Result<Vec<_>, _>>()
1166 .expect("reader path");
1167 assert_eq!(from_reader[0].element_str(1), Some("PO-4711"));
1168 }
1169
1170 #[test]
1171 fn an_explicit_service_string_advice_outranks_the_una_in_the_input() {
1172 let ssa = ServiceStringAdvice::default();
1173 let config = ReaderConfig::default().with_service_string_advice(ssa);
1174 let input = b"UNA:;.? 'BGM+220'";
1176
1177 for segments in [
1178 crate::from_bytes_with_config(input, config)
1179 .map(|r| r.map(crate::Segment::into_owned))
1180 .collect::<Result<Vec<_>, _>>()
1181 .expect("slice path"),
1182 from_reader_with_config(std::io::Cursor::new(&input[..]), config)
1183 .collect::<Result<Vec<_>, _>>()
1184 .expect("reader path"),
1185 ] {
1186 assert_eq!(segments[0].element_str(0).unwrap(), "220");
1187 }
1188 }
1189
1190 #[test]
1193 fn both_parsing_paths_agree_on_every_edge_case() {
1194 fn bom(rest: &[u8]) -> Vec<u8> {
1195 let mut v = crate::tokenizer::UTF8_BOM.to_vec();
1196 v.extend_from_slice(rest);
1197 v
1198 }
1199
1200 let cases: Vec<Vec<u8>> = vec![
1201 b"BGM+220".to_vec(), b"BGM+220'".to_vec(), b"BGM+220'\r\n".to_vec(), b"\n UNA:+.? 'BGM+220'".to_vec(), bom(b"UNA:+.? 'BGM+220'"), bom(b"UNB+UNOA:1+S+R+200101:0900+1'UNZ+0+1'"), b"UNB+UNOC:4+S+R+260101:0900+I'RFF+ON:1*ON:2'UNZ+0+I'".to_vec(),
1208 b"FTX+AAA++dangling?".to_vec(), b"UNA::.? 'BGM:220'".to_vec(), ];
1211
1212 for input in cases {
1213 let readable = String::from_utf8_lossy(&input).into_owned();
1214 let sliced: Result<Vec<OwnedSegment>, _> = crate::from_bytes(&input)
1215 .map(|r| r.map(|s| s.into_owned()))
1216 .collect();
1217 let streamed: Result<Vec<OwnedSegment>, _> =
1218 crate::from_reader(std::io::Cursor::new(input.clone())).collect();
1219 assert_eq!(
1220 format!("{sliced:?}"),
1221 format!("{streamed:?}"),
1222 "slice and reader paths disagree on {readable:?}",
1223 );
1224 }
1225 }
1226
1227 #[test]
1228 fn a_segment_with_no_terminator_is_rejected_not_accepted() {
1229 for input in [
1232 &b"BGM+220"[..],
1233 &b"UNB+UNOA:1+S+R+200101:0900+1'UNZ+0+1"[..],
1234 ] {
1235 let err = crate::from_bytes(input)
1236 .collect::<Result<Vec<_>, _>>()
1237 .expect_err("an unterminated segment must not parse");
1238 assert!(
1239 matches!(err, EdifactError::UnexpectedEof { .. }),
1240 "expected UnexpectedEof for {:?}, got {err:?}",
1241 std::str::from_utf8(input).unwrap(),
1242 );
1243 }
1244 }
1245
1246 #[test]
1247 fn a_byte_order_mark_does_not_hide_the_first_segment() {
1248 let mut input = crate::tokenizer::UTF8_BOM.to_vec();
1249 input.extend_from_slice(b"UNB+UNOA:1+S+R+200101:0900+1'UNZ+0+1'");
1250 let segments: Vec<_> = crate::from_bytes(&input)
1251 .collect::<Result<Vec<_>, _>>()
1252 .expect("a BOM is prologue, not a segment tag");
1253 assert_eq!(
1254 segments.iter().map(Segment::tag).collect::<Vec<_>>(),
1255 ["UNB", "UNZ"],
1256 );
1257 }
1258
1259 #[test]
1260 fn from_reader_rejects_invalid_una() {
1261 let input = b"UNA::.? 'BGM:220'";
1262 let err = crate::from_reader(std::io::Cursor::new(input))
1263 .collect::<Result<Vec<_>, _>>()
1264 .expect_err("invalid UNA should fail reader parsing");
1265 assert!(matches!(err, EdifactError::InvalidUna));
1266 }
1267}