1#[derive(Debug, Clone, PartialEq, Eq)]
3pub enum UnaParseError {
4 InvalidLength { expected: usize, actual: usize },
6 InvalidPrefix,
8}
9
10impl std::fmt::Display for UnaParseError {
11 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 match self {
13 Self::InvalidLength { expected, actual } => {
14 write!(
15 f,
16 "UNA segment must be exactly {expected} bytes, got {actual}"
17 )
18 }
19 Self::InvalidPrefix => write!(f, "UNA segment must start with 'UNA'"),
20 }
21 }
22}
23
24impl std::error::Error for UnaParseError {}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct EdifactDelimiters {
38 pub component: u8,
40 pub element: u8,
42 pub decimal: u8,
44 pub release: u8,
46 pub segment: u8,
48 pub reserved: u8,
50}
51
52impl Default for EdifactDelimiters {
53 fn default() -> Self {
54 Self {
55 component: b':',
56 element: b'+',
57 decimal: b'.',
58 release: b'?',
59 segment: b'\'',
60 reserved: b' ',
61 }
62 }
63}
64
65impl EdifactDelimiters {
66 pub const STANDARD: Self = Self {
68 component: b':',
69 element: b'+',
70 decimal: b'.',
71 release: b'?',
72 segment: b'\'',
73 reserved: b' ',
74 };
75
76 pub fn from_una(una: &[u8]) -> Result<Self, UnaParseError> {
85 if una.len() != 9 {
86 return Err(UnaParseError::InvalidLength {
87 expected: 9,
88 actual: una.len(),
89 });
90 }
91
92 if &una[0..3] != b"UNA" {
93 return Err(UnaParseError::InvalidPrefix);
94 }
95
96 Ok(Self {
105 component: una[3],
106 element: una[4],
107 decimal: una[5],
108 release: una[6],
109 reserved: una[7],
110 segment: una[8],
111 })
112 }
113
114 pub fn detect(input: &[u8]) -> (bool, Self) {
121 if input.len() >= 9 && &input[0..3] == b"UNA" {
122 match Self::from_una(&input[0..9]) {
123 Ok(d) => (true, d),
124 Err(_) => (false, Self::default()),
125 }
126 } else {
127 (false, Self::default())
128 }
129 }
130
131 pub fn to_una_string(&self) -> String {
135 format!(
136 "UNA{}{}{}{}{}{}",
137 self.component as char,
138 self.element as char,
139 self.decimal as char,
140 self.release as char,
141 self.reserved as char,
142 self.segment as char,
143 )
144 }
145}
146
147impl std::fmt::Display for EdifactDelimiters {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(
150 f,
151 "UNA{}{}{}{}{}{}",
152 self.component as char,
153 self.element as char,
154 self.decimal as char,
155 self.release as char,
156 self.reserved as char,
157 self.segment as char,
158 )
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_default_delimiters() {
168 let d = EdifactDelimiters::default();
169 assert_eq!(d.component, b':');
170 assert_eq!(d.element, b'+');
171 assert_eq!(d.decimal, b'.');
172 assert_eq!(d.release, b'?');
173 assert_eq!(d.segment, b'\'');
174 assert_eq!(d.reserved, b' ');
175 }
176
177 #[test]
178 fn test_delimiters_equality() {
179 let a = EdifactDelimiters::default();
180 let b = EdifactDelimiters::default();
181 assert_eq!(a, b);
182 }
183
184 #[test]
185 fn test_delimiters_debug() {
186 let d = EdifactDelimiters::default();
187 let debug = format!("{:?}", d);
188 assert!(debug.contains("EdifactDelimiters"));
189 }
190
191 #[test]
192 fn test_from_una_standard() {
193 let una = b"UNA:+.? '";
194 let d = EdifactDelimiters::from_una(una).unwrap();
195 assert_eq!(d, EdifactDelimiters::default());
196 }
197
198 #[test]
199 fn test_from_una_custom_delimiters() {
200 let una = b"UNA;*.# |";
201 let d = EdifactDelimiters::from_una(una).unwrap();
202 assert_eq!(d.component, b';');
203 assert_eq!(d.element, b'*');
204 assert_eq!(d.decimal, b'.');
205 assert_eq!(d.release, b'#');
206 assert_eq!(d.reserved, b' ');
207 assert_eq!(d.segment, b'|');
208 }
209
210 #[test]
211 fn test_from_una_too_short() {
212 let una = b"UNA:+.";
213 assert!(EdifactDelimiters::from_una(una).is_err());
214 }
215
216 #[test]
217 fn test_from_una_wrong_prefix() {
218 let una = b"XXX:+.? '";
219 assert!(EdifactDelimiters::from_una(una).is_err());
220 }
221
222 #[test]
223 fn test_detect_with_una() {
224 let input = b"UNA:+.? 'UNB+UNOC:3+sender+recipient'";
225 let (has_una, delimiters) = EdifactDelimiters::detect(input);
226 assert!(has_una);
227 assert_eq!(delimiters, EdifactDelimiters::default());
228 }
229
230 #[test]
231 fn test_detect_without_una() {
232 let input = b"UNB+UNOC:3+sender+recipient'";
233 let (has_una, delimiters) = EdifactDelimiters::detect(input);
234 assert!(!has_una);
235 assert_eq!(delimiters, EdifactDelimiters::default());
236 }
237
238 #[test]
239 fn test_detect_empty_input() {
240 let input = b"";
241 let (has_una, delimiters) = EdifactDelimiters::detect(input);
242 assert!(!has_una);
243 assert_eq!(delimiters, EdifactDelimiters::default());
244 }
245
246 #[test]
247 fn test_una_roundtrip() {
248 let original = EdifactDelimiters {
249 component: b';',
250 element: b'*',
251 decimal: b',',
252 release: b'#',
253 segment: b'!',
254 reserved: b' ',
255 };
256 let una_string = original.to_una_string();
257 let parsed = EdifactDelimiters::from_una(una_string.as_bytes()).unwrap();
258 assert_eq!(original, parsed);
259 }
260}