1use crate::{error::EdifactError, model::Segment, tokenizer::ServiceStringAdvice};
4use std::io::Write;
5
6pub struct Writer<W: Write> {
11 inner: W,
12 ssa: ServiceStringAdvice,
13 segment_count: u64,
16}
17
18impl<W: Write> Writer<W> {
19 pub fn new(inner: W) -> Self {
21 Self {
22 inner,
23 ssa: ServiceStringAdvice::default(),
24 segment_count: 0,
25 }
26 }
27
28 pub fn with_una(mut inner: W, ssa: ServiceStringAdvice) -> Result<Self, EdifactError> {
30 if !ssa.is_valid() {
33 return Err(EdifactError::InvalidUna);
34 }
35 let una = [
37 b'U',
38 b'N',
39 b'A',
40 ssa.component_sep,
41 ssa.element_sep,
42 ssa.decimal_mark,
43 ssa.release_char,
44 b' ',
45 ssa.segment_term,
46 ];
47 inner.write_all(&una)?;
48 Ok(Self {
49 inner,
50 ssa,
51 segment_count: 0,
52 })
53 }
54
55 pub fn write_segment(&mut self, seg: &Segment<'_>) -> Result<(), EdifactError> {
57 self.inner.write_all(seg.tag.as_bytes())?;
59
60 for element in &seg.elements {
61 self.inner.write_all(&[self.ssa.element_sep])?;
63 let mut first_component = true;
64 for component in &element.components {
65 if !first_component {
66 self.inner.write_all(&[self.ssa.component_sep])?;
67 }
68 first_component = false;
69 self.write_escaped(component)?;
70 }
71 }
72
73 self.inner.write_all(&[self.ssa.segment_term])?;
75 self.segment_count += 1;
76 Ok(())
77 }
78
79 pub fn write_raw(&mut self, tag: &str, elements: &[&str]) -> Result<(), EdifactError> {
100 self.inner.write_all(tag.as_bytes())?;
101 let comp_sep = self.ssa.component_sep;
102 for el in elements {
103 self.inner.write_all(&[self.ssa.element_sep])?;
104 let mut parts = el.as_bytes().split(|&b| b == comp_sep);
106 if let Some(first) = parts.next() {
107 self.write_escaped(
110 std::str::from_utf8(first).map_err(|_| EdifactError::InvalidUtf8)?,
111 )?;
112 }
113 for part in parts {
114 self.inner.write_all(&[comp_sep])?;
115 self.write_escaped(
116 std::str::from_utf8(part).map_err(|_| EdifactError::InvalidUtf8)?,
117 )?;
118 }
119 }
120 self.inner.write_all(&[self.ssa.segment_term])?;
121 self.segment_count += 1;
122 Ok(())
123 }
124
125 pub fn write_segment_parts<E>(&mut self, tag: &str, elements: &[E]) -> Result<(), EdifactError>
131 where
132 E: AsRef<[String]>,
133 {
134 self.inner.write_all(tag.as_bytes())?;
135 for element in elements {
136 self.inner.write_all(&[self.ssa.element_sep])?;
137 let mut first = true;
138 for comp in element.as_ref() {
139 if !first {
140 self.inner.write_all(&[self.ssa.component_sep])?;
141 }
142 first = false;
143 self.write_escaped(comp.as_str())?;
144 }
145 }
146 self.inner.write_all(&[self.ssa.segment_term])?;
147 self.segment_count += 1;
148 Ok(())
149 }
150
151 pub fn finish(mut self) -> Result<W, EdifactError> {
153 self.inner.flush()?;
154 Ok(self.inner)
155 }
156
157 pub fn finish_unt(mut self, message_ref: &str) -> Result<W, EdifactError> {
168 let count = self.segment_count + 1;
170 let count_str = count.to_string();
171 self.write_raw("UNT", &[count_str.as_str(), message_ref])?;
172 self.finish()
173 }
174
175 pub fn segment_count(&self) -> u64 {
177 self.segment_count
178 }
179
180 pub fn service_string_advice(&self) -> ServiceStringAdvice {
182 self.ssa
183 }
184 #[inline]
188 pub(crate) fn write_tag_only(&mut self, tag: &str) -> Result<(), EdifactError> {
189 self.inner.write_all(tag.as_bytes())?;
190 Ok(())
191 }
192
193 #[inline]
195 pub(crate) fn write_element_sep(&mut self) -> Result<(), EdifactError> {
196 self.inner.write_all(&[self.ssa.element_sep])?;
197 Ok(())
198 }
199
200 #[inline]
202 pub(crate) fn write_component_sep(&mut self) -> Result<(), EdifactError> {
203 self.inner.write_all(&[self.ssa.component_sep])?;
204 Ok(())
205 }
206
207 #[inline]
209 pub(crate) fn write_segment_term_and_count(&mut self) -> Result<(), EdifactError> {
210 self.inner.write_all(&[self.ssa.segment_term])?;
211 self.segment_count += 1;
212 Ok(())
213 }
214
215 pub(crate) fn write_escaped(&mut self, value: &str) -> Result<(), EdifactError> {
217 let (elem, comp, release, term) = (
218 self.ssa.element_sep,
219 self.ssa.component_sep,
220 self.ssa.release_char,
221 self.ssa.segment_term,
222 );
223 let bytes = value.as_bytes();
224 let mut last = 0;
225 let mut pos = 0;
226 while pos < bytes.len() {
227 let remaining = &bytes[pos..];
230 let hit_ecr = memchr::memchr3(elem, comp, release, remaining);
231 let hit_t = memchr::memchr(term, remaining);
232 let hit = match (hit_ecr, hit_t) {
233 (None, None) => break,
234 (Some(a), None) => a,
235 (None, Some(b)) => b,
236 (Some(a), Some(b)) => a.min(b),
237 };
238 let abs = pos + hit;
239 if abs > last {
240 self.inner.write_all(&bytes[last..abs])?;
241 }
242 self.inner.write_all(&[release, bytes[abs]])?;
243 last = abs + 1;
244 pos = abs + 1;
245 }
246 self.inner.write_all(&bytes[last..])?;
247 Ok(())
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::model::Element;
255
256 #[test]
257 fn write_and_parse_simple_segment() {
258 let segs: Vec<Segment<'static>> = vec![Segment::new(
259 "BGM",
260 vec![Element::of(&["220"]), Element::of(&["ORDER123"])],
261 )];
262 let bytes = crate::segments_to_bytes(&segs).unwrap();
263 let s = std::str::from_utf8(&bytes).unwrap();
264 assert!(s.starts_with("BGM+220+ORDER123'"));
265 }
266
267 #[test]
268 fn release_char_escaped() {
269 let segs: Vec<Segment<'static>> = vec![Segment::new(
270 "FTX",
271 vec![Element::of(&["value+with+delimiters"])],
272 )];
273 let bytes = crate::segments_to_bytes(&segs).unwrap();
274 let s = std::str::from_utf8(&bytes).unwrap();
275 assert!(s.contains("?+"), "escape missing: {s}");
277 }
278
279 #[test]
280 fn round_trip_preserves_values() {
281 let segs: Vec<Segment<'static>> = vec![
282 Segment::new(
283 "UNB",
284 vec![
285 Element::of(&["UNOA", "1"]),
286 Element::of(&["SENDER"]),
287 Element::of(&["RECEIVER"]),
288 ],
289 ),
290 Segment::new("UNZ", vec![Element::of(&["0"]), Element::of(&["1"])]),
291 ];
292 let bytes = crate::segments_to_bytes(&segs).unwrap();
293 let rt: Vec<crate::OwnedSegment> = crate::parser::from_reader(std::io::Cursor::new(&bytes))
294 .expect("round-trip parse failed");
295 assert_eq!(rt[0].tag, "UNB");
296 assert_eq!(rt[0].as_borrowed().element_str(0), Some("UNOA"));
297 assert_eq!(rt[1].tag, "UNZ");
298 }
299
300 #[test]
304 fn with_una_non_default_delimiters() {
305 use crate::tokenizer::ServiceStringAdvice;
306
307 let ssa = ServiceStringAdvice {
309 component_sep: b'|',
310 element_sep: b'!',
311 release_char: b'?',
312 decimal_mark: b',',
313 segment_term: b'~',
314 };
315
316 let buf = Vec::new();
317 let mut writer = Writer::with_una(buf, ssa).expect("writer creation failed");
318
319 writer
321 .write_segment_parts(
322 "BGM",
323 &[
324 vec!["220".to_owned(), "SUB1".to_owned()],
325 vec!["PO1".to_owned()],
326 ],
327 )
328 .expect("write failed");
329
330 let out = writer.finish().expect("finish failed");
331 let s = std::str::from_utf8(&out).unwrap();
332
333 assert!(s.contains("BGM"), "BGM segment missing: {s}");
336 let after_una = s.find("BGM").map(|i| &s[i..]).unwrap_or(s);
338 assert!(
339 after_una.contains('!'),
340 "missing element sep in segment: {after_una}"
341 );
342 assert!(
343 after_una.contains('|'),
344 "missing component sep in segment: {after_una}"
345 );
346 assert!(
347 after_una.ends_with('~'),
348 "missing segment term in segment: {after_una}"
349 );
350 assert!(s.contains(','), "missing decimal mark in UNA: {s}");
352 assert!(!s.contains('+'), "default element sep leaked: {s}");
353 assert!(!s.contains(':'), "default component sep leaked: {s}");
354 assert!(
356 !after_una.contains('\''),
357 "default segment term leaked after UNA: {after_una}"
358 );
359 }
360}