Skip to main content

edifact_parser/
segment_builder.rs

1use edifact_primitives::{EdifactDelimiters, RawSegment, SegmentPosition};
2
3use crate::EdifactTokenizer;
4
5/// Builds `RawSegment` instances from raw segment strings.
6///
7/// Takes the tokenized segment string (e.g., "NAD+Z04+9900123000002::293")
8/// and splits it into the segment ID, elements, and components.
9pub struct SegmentBuilder {
10    tokenizer: EdifactTokenizer,
11}
12
13impl SegmentBuilder {
14    /// Creates a new segment builder with the given delimiters.
15    pub fn new(delimiters: EdifactDelimiters) -> Self {
16        Self {
17            tokenizer: EdifactTokenizer::new(delimiters),
18        }
19    }
20
21    /// Parses a raw segment string into a `RawSegment`.
22    ///
23    /// The input is a single segment WITHOUT its terminator character.
24    /// Example: `"NAD+Z04+9900123000002::293"`
25    ///
26    /// Returns `None` if the segment string is empty.
27    pub fn build<'a>(
28        &self,
29        segment_str: &'a str,
30        position: SegmentPosition,
31    ) -> Option<RawSegment<'a>> {
32        if segment_str.is_empty() {
33            return None;
34        }
35
36        let mut elements_iter = self.tokenizer.tokenize_elements(segment_str);
37
38        // First element is the segment ID
39        let id = elements_iter.next()?;
40        if id.is_empty() {
41            return None;
42        }
43
44        // Remaining elements are data elements, each split into components
45        let mut elements = Vec::new();
46        for element_str in elements_iter {
47            let components: Vec<&'a str> =
48                self.tokenizer.tokenize_components(element_str).collect();
49            elements.push(components);
50        }
51
52        Some(RawSegment::new(id, elements, position))
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    fn pos(n: u32, offset: usize) -> SegmentPosition {
61        SegmentPosition::new(n, offset, 1)
62    }
63
64    #[test]
65    fn test_build_simple_segment() {
66        let builder = SegmentBuilder::new(EdifactDelimiters::default());
67        let seg = builder
68            .build("UNH+00001+UTILMD:D:11A:UN:S2.1", pos(1, 0))
69            .unwrap();
70
71        assert_eq!(seg.id, "UNH");
72        assert_eq!(seg.element_count(), 2);
73        assert_eq!(seg.get_element(0), "00001");
74        assert_eq!(seg.get_component(1, 0), "UTILMD");
75        assert_eq!(seg.get_component(1, 1), "D");
76        assert_eq!(seg.get_component(1, 2), "11A");
77        assert_eq!(seg.get_component(1, 3), "UN");
78        assert_eq!(seg.get_component(1, 4), "S2.1");
79    }
80
81    #[test]
82    fn test_build_nad_segment() {
83        let builder = SegmentBuilder::new(EdifactDelimiters::default());
84        let seg = builder
85            .build("NAD+Z04+9900123000002::293", pos(5, 100))
86            .unwrap();
87
88        assert_eq!(seg.id, "NAD");
89        assert_eq!(seg.get_element(0), "Z04");
90        assert_eq!(seg.get_component(1, 0), "9900123000002");
91        assert_eq!(seg.get_component(1, 1), "");
92        assert_eq!(seg.get_component(1, 2), "293");
93    }
94
95    #[test]
96    fn test_build_dtm_with_escaped_plus() {
97        let builder = SegmentBuilder::new(EdifactDelimiters::default());
98        let seg = builder
99            .build("DTM+137:202501010000?+01:303", pos(3, 50))
100            .unwrap();
101
102        assert_eq!(seg.id, "DTM");
103        assert_eq!(seg.get_component(0, 0), "137");
104        assert_eq!(seg.get_component(0, 1), "202501010000?+01");
105        assert_eq!(seg.get_component(0, 2), "303");
106    }
107
108    #[test]
109    fn test_build_segment_no_elements() {
110        let builder = SegmentBuilder::new(EdifactDelimiters::default());
111        let seg = builder.build("UNA", pos(1, 0)).unwrap();
112
113        assert_eq!(seg.id, "UNA");
114        assert_eq!(seg.element_count(), 0);
115    }
116
117    #[test]
118    fn test_build_empty_input() {
119        let builder = SegmentBuilder::new(EdifactDelimiters::default());
120        assert!(builder.build("", pos(1, 0)).is_none());
121    }
122
123    #[test]
124    fn test_build_loc_segment() {
125        let builder = SegmentBuilder::new(EdifactDelimiters::default());
126        let seg = builder
127            .build("LOC+Z16+DE00014545768S0000000000000003054", pos(8, 200))
128            .unwrap();
129
130        assert_eq!(seg.id, "LOC");
131        assert_eq!(seg.get_element(0), "Z16");
132        assert_eq!(seg.get_element(1), "DE00014545768S0000000000000003054");
133    }
134
135    #[test]
136    fn test_build_preserves_position() {
137        let builder = SegmentBuilder::new(EdifactDelimiters::default());
138        let seg = builder.build("BGM+E03+DOC001", pos(2, 42)).unwrap();
139
140        assert_eq!(seg.position.segment_number, 2);
141        assert_eq!(seg.position.byte_offset, 42);
142        assert_eq!(seg.position.message_number, 1);
143    }
144
145    #[test]
146    fn test_build_rff_segment() {
147        let builder = SegmentBuilder::new(EdifactDelimiters::default());
148        let seg = builder.build("RFF+Z13:TXREF001", pos(10, 300)).unwrap();
149
150        assert_eq!(seg.id, "RFF");
151        assert_eq!(seg.get_component(0, 0), "Z13");
152        assert_eq!(seg.get_component(0, 1), "TXREF001");
153    }
154}