1pub mod errors;
2
3use errors::DelimiterError;
4
5const DEFAULT_SEGMENT_TERMINATOR: u8 = b'~';
6const DEFAULT_ELEMENT_SEPARATOR: u8 = b'*';
7const DEFAULT_SUB_ELEMENT_SEPARATOR: u8 = b':';
8
9const ISA_MIN_LENGTH: usize = 106;
10const ISA_ELEMENT_SEPARATOR_INDEX: usize = 3;
11const ISA_SUB_ELEMENT_SEPARATOR_INDEX: usize = 104;
12const ISA_SEGMENT_TERMINATOR_INDEX: usize = 105;
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
22pub struct Delimiters {
23 segment_terminator: u8,
24 element_separator: u8,
25 sub_element_separator: u8,
26}
27
28impl Delimiters {
29 pub fn new(segment_terminator: u8, element_separator: u8, sub_element_separator: u8) -> Self {
36 Delimiters {
37 segment_terminator,
38 element_separator,
39 sub_element_separator,
40 }
41 }
42
43 pub fn from_isa(isa_segment: &[u8]) -> Result<Self, DelimiterError> {
59 if isa_segment.len() < ISA_MIN_LENGTH {
60 return Err(DelimiterError::InvalidIsaLength);
61 }
62
63 let element_separator = isa_segment[ISA_ELEMENT_SEPARATOR_INDEX];
64 let sub_element_separator = isa_segment[ISA_SUB_ELEMENT_SEPARATOR_INDEX];
65 let segment_terminator = isa_segment[ISA_SEGMENT_TERMINATOR_INDEX];
66
67 Ok(Delimiters {
68 element_separator,
69 sub_element_separator,
70 segment_terminator,
71 })
72 }
73
74 pub fn segment_terminator(&self) -> u8 {
76 self.segment_terminator
77 }
78
79 pub fn element_separator(&self) -> u8 {
81 self.element_separator
82 }
83
84 pub fn sub_element_separator(&self) -> u8 {
86 self.sub_element_separator
87 }
88
89 pub fn are_valid(&self) -> bool {
96 self.segment_terminator != self.element_separator &&
97 self.segment_terminator != self.sub_element_separator &&
98 self.element_separator != self.sub_element_separator
99 }
100}
101
102impl Default for Delimiters {
103 fn default() -> Self {
105 Delimiters {
106 segment_terminator: DEFAULT_SEGMENT_TERMINATOR,
107 element_separator: DEFAULT_ELEMENT_SEPARATOR,
108 sub_element_separator: DEFAULT_SUB_ELEMENT_SEPARATOR,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 const SAMPLE_ISA_SEGMENT_STANDARD: &[u8] = b"ISA*00* *00* *ZZ*SENDERID *ZZ*RECEIVERID *250403*0856*U*00501*000000001*0*P*:~";
118 const SAMPLE_ISA_SEGMENT_ALT: &[u8] = b"ISA^00^ ^00^ ^ZZ^SENDERID ^ZZ^RECEIVERID ^250403^0856^U^00401^000000002^1^T^>}";
119 const TOO_SHORT_ISA: &[u8] = b"ISA*00*";
120
121 #[test]
122 fn test_default_delimiters() {
123 let delimiters = Delimiters::default();
124 assert_eq!(delimiters.segment_terminator(), b'~');
125 assert_eq!(delimiters.element_separator(), b'*');
126 assert_eq!(delimiters.sub_element_separator(), b':');
127 }
128
129 #[test]
130 fn test_new_delimiters() {
131 let delimiters = Delimiters::new(b'!', b'@', b'#');
132 assert_eq!(delimiters.segment_terminator(), b'!');
133 assert_eq!(delimiters.element_separator(), b'@');
134 assert_eq!(delimiters.sub_element_separator(), b'#');
135 }
136
137 #[test]
138 fn test_from_isa_standard() {
139 let result = Delimiters::from_isa(SAMPLE_ISA_SEGMENT_STANDARD);
140 assert!(result.is_ok());
141 let delimiters = result.unwrap();
142 assert_eq!(delimiters.segment_terminator(), b'~');
143 assert_eq!(delimiters.element_separator(), b'*');
144 assert_eq!(delimiters.sub_element_separator(), b':');
145 }
146
147 #[test]
148 fn test_from_isa_alternative() {
149 let result = Delimiters::from_isa(SAMPLE_ISA_SEGMENT_ALT);
150 assert!(result.is_ok());
151 let delimiters = result.unwrap();
152 assert_eq!(delimiters.segment_terminator(), b'}');
153 assert_eq!(delimiters.element_separator(), b'^');
154 assert_eq!(delimiters.sub_element_separator(), b'>');
155 }
156
157 #[test]
158 fn test_from_isa_too_short() {
159 let result = Delimiters::from_isa(TOO_SHORT_ISA);
160 assert!(result.is_err());
161 assert_eq!(result.err().unwrap(), DelimiterError::InvalidIsaLength);
162 }
163
164 #[test]
165 fn test_from_isa_exact_length() {
166 let exact_len_isa = SAMPLE_ISA_SEGMENT_STANDARD[..ISA_MIN_LENGTH].to_vec();
167 assert_eq!(exact_len_isa.len(), ISA_MIN_LENGTH);
168
169 let result = Delimiters::from_isa(&exact_len_isa);
170 assert!(result.is_ok());
171 let delimiters = result.unwrap();
172 assert_eq!(delimiters.segment_terminator(), b'~');
173 assert_eq!(delimiters.element_separator(), b'*');
174 assert_eq!(delimiters.sub_element_separator(), b':');
175 }
176
177 #[test]
178 fn test_getters() {
179 let delimiters = Delimiters::new(b'A', b'B', b'C');
180 assert_eq!(delimiters.segment_terminator(), b'A');
181 assert_eq!(delimiters.element_separator(), b'B');
182 assert_eq!(delimiters.sub_element_separator(), b'C');
183 }
184
185 #[test]
186 fn test_are_valid() {
187 let valid_delimiters = Delimiters::new(b'~', b'*', b':');
188 assert!(valid_delimiters.are_valid());
189
190 let invalid_delimiters1 = Delimiters::new(b'*', b'*', b':');
191 assert!(!invalid_delimiters1.are_valid());
192
193 let invalid_delimiters2 = Delimiters::new(b'~', b'*', b'*');
194 assert!(!invalid_delimiters2.are_valid());
195
196 let invalid_delimiters3 = Delimiters::new(b'~', b'~', b':');
197 assert!(!invalid_delimiters3.are_valid());
198 }
199
200 use proptest::prelude::*;
201
202 fn valid_delimiter() -> impl Strategy<Value = u8> {
203 (33..=126u8).prop_filter("Avoiding whitespace", |&c| c != b' ' && c != b'\t' && c != b'\n' && c != b'\r')
204 }
205
206 fn distinct_delimiters() -> impl Strategy<Value = (u8, u8, u8)> {
207 (valid_delimiter(), valid_delimiter(), valid_delimiter())
208 .prop_filter("Delimiters must be distinct", |(a, b, c)| a != b && b != c && a != c)
209 }
210
211 fn isa_segment_with_delimiters() -> impl Strategy<Value = (Vec<u8>, u8, u8, u8)> {
212 distinct_delimiters().prop_flat_map(|(elem_sep, sub_elem_sep, seg_term)| {
213 let mut isa = Vec::with_capacity(ISA_MIN_LENGTH);
214 isa.extend_from_slice(b"ISA");
215 isa.push(elem_sep);
216
217 for i in 4..ISA_SUB_ELEMENT_SEPARATOR_INDEX {
218 if i % 2 == 0 {
219 isa.push(elem_sep);
220 } else {
221 isa.push(b'X');
222 }
223 }
224
225 while isa.len() < ISA_SUB_ELEMENT_SEPARATOR_INDEX {
226 isa.push(b'X');
227 }
228
229 isa.push(sub_elem_sep);
230 isa.push(seg_term);
231
232 Just((isa, elem_sep, sub_elem_sep, seg_term))
233 })
234 }
235
236 fn isa_segment_extended() -> impl Strategy<Value = (Vec<u8>, u8, u8, u8)> {
237 isa_segment_with_delimiters().prop_flat_map(|(isa, elem_sep, sub_elem_sep, seg_term)| {
238 (0..=10).prop_map(move |n| {
239 let mut extended_isa = isa.clone();
240 for _ in 0..n {
241 extended_isa.push(b'X');
242 }
243 (extended_isa, elem_sep, sub_elem_sep, seg_term)
244 })
245 })
246 }
247
248 fn invalid_length_isa() -> impl Strategy<Value = Vec<u8>> {
249 (1..ISA_MIN_LENGTH).prop_map(|len| {
250 let mut isa = Vec::with_capacity(len);
251 isa.extend_from_slice(b"ISA*");
252 while isa.len() < len {
253 isa.push(b'X');
254 }
255 isa
256 })
257 }
258
259 proptest! {
260 #[test]
261 fn prop_from_isa_extracts_correct_delimiters(
262 (isa, elem_sep, sub_elem_sep, seg_term) in isa_segment_with_delimiters()
263 ) {
264 let result = Delimiters::from_isa(&isa);
265 prop_assert!(result.is_ok(), "from_isa should succeed on valid ISA segment");
266
267 let delimiters = result.unwrap();
268 prop_assert_eq!(delimiters.element_separator(), elem_sep);
269 prop_assert_eq!(delimiters.sub_element_separator(), sub_elem_sep);
270 prop_assert_eq!(delimiters.segment_terminator(), seg_term);
271 }
272
273 #[test]
274 fn prop_from_isa_works_with_extended_segments(
275 (isa, elem_sep, sub_elem_sep, seg_term) in isa_segment_extended()
276 ) {
277 let result = Delimiters::from_isa(&isa);
278 prop_assert!(result.is_ok(), "from_isa should succeed on extended ISA segment");
279
280 let delimiters = result.unwrap();
281 prop_assert_eq!(delimiters.element_separator(), elem_sep);
282 prop_assert_eq!(delimiters.sub_element_separator(), sub_elem_sep);
283 prop_assert_eq!(delimiters.segment_terminator(), seg_term);
284 }
285
286 #[test]
287 fn prop_new_delimiters_preserves_values(
288 (seg_term, elem_sep, sub_elem_sep) in distinct_delimiters()
289 ) {
290 let delimiters = Delimiters::new(seg_term, elem_sep, sub_elem_sep);
291 prop_assert_eq!(delimiters.segment_terminator(), seg_term);
292 prop_assert_eq!(delimiters.element_separator(), elem_sep);
293 prop_assert_eq!(delimiters.sub_element_separator(), sub_elem_sep);
294 }
295
296 #[test]
297 fn prop_delimiter_roundtrip(
298 (seg_term, elem_sep, sub_elem_sep) in distinct_delimiters()
299 ) {
300 let delimiters1 = Delimiters::new(seg_term, elem_sep, sub_elem_sep);
301
302 let delimiters2 = Delimiters::new(
303 delimiters1.segment_terminator(),
304 delimiters1.element_separator(),
305 delimiters1.sub_element_separator()
306 );
307
308 prop_assert_eq!(delimiters1, delimiters2);
309 }
310
311 #[test]
312 fn prop_delimiter_equality(
313 (s1, e1, se1) in distinct_delimiters(),
314 (s2, e2, se2) in distinct_delimiters()
315 ) {
316 let d1 = Delimiters::new(s1, e1, se1);
317 let d2 = Delimiters::new(s1, e1, se1);
318 let d3 = Delimiters::new(s2, e2, se2);
319
320 prop_assert_eq!(d1, d2);
321
322 if s1 != s2 || e1 != e2 || se1 != se2 {
323 prop_assert_ne!(d1, d3);
324 }
325 }
326
327 #[test]
328 fn prop_invalid_length_isa_returns_error(
329 isa in invalid_length_isa()
330 ) {
331 let result = Delimiters::from_isa(&isa);
332 prop_assert!(result.is_err());
333 prop_assert_eq!(result.err().unwrap(), DelimiterError::InvalidIsaLength);
334 }
335
336 #[test]
337 fn prop_valid_delimiters_check(
338 (seg_term, elem_sep, sub_elem_sep) in distinct_delimiters()
339 ) {
340 let valid = Delimiters::new(seg_term, elem_sep, sub_elem_sep);
341 prop_assert!(valid.are_valid());
342
343 let invalid1 = Delimiters::new(seg_term, seg_term, sub_elem_sep);
344 prop_assert!(!invalid1.are_valid());
345
346 let invalid2 = Delimiters::new(seg_term, elem_sep, seg_term);
347 prop_assert!(!invalid2.are_valid());
348
349 let invalid3 = Delimiters::new(seg_term, elem_sep, elem_sep);
350 prop_assert!(!invalid3.are_valid());
351 }
352 }
353}