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: u32,
14}
15
16impl<W: Write> Writer<W> {
17 pub fn new(inner: W) -> Self {
19 Self {
20 inner,
21 ssa: ServiceStringAdvice::default(),
22 segment_count: 0,
23 }
24 }
25
26 pub fn with_una(mut inner: W, ssa: ServiceStringAdvice) -> Result<Self, EdifactError> {
28 if [ssa.component_sep, ssa.element_sep, ssa.decimal_mark, ssa.release_char, ssa.segment_term]
31 .iter()
32 .any(|&b| b > 0x7F)
33 {
34 return Err(EdifactError::InvalidUna);
35 }
36 let una = [
38 b'U',
39 b'N',
40 b'A',
41 ssa.component_sep,
42 ssa.element_sep,
43 ssa.decimal_mark,
44 ssa.release_char,
45 b' ',
46 ssa.segment_term,
47 ];
48 inner.write_all(&una)?;
49 Ok(Self {
50 inner,
51 ssa,
52 segment_count: 0,
53 })
54 }
55
56 pub fn write_segment(&mut self, seg: &Segment<'_>) -> Result<(), EdifactError> {
58 self.inner.write_all(seg.tag.as_bytes())?;
60
61 for element in &seg.elements {
62 self.inner.write_all(&[self.ssa.element_sep])?;
64 let mut first_component = true;
65 for component in &element.components {
66 if !first_component {
67 self.inner.write_all(&[self.ssa.component_sep])?;
68 }
69 first_component = false;
70 self.write_escaped(component)?;
71 }
72 }
73
74 self.inner.write_all(&[self.ssa.segment_term])?;
76 self.segment_count += 1;
77 Ok(())
78 }
79
80 pub fn write_raw(&mut self, tag: &str, elements: &[&str]) -> Result<(), EdifactError> {
101 self.inner.write_all(tag.as_bytes())?;
102 let comp_sep = self.ssa.component_sep;
103 for el in elements {
104 self.inner.write_all(&[self.ssa.element_sep])?;
105 let mut parts = el.as_bytes().split(|&b| b == comp_sep);
107 if let Some(first) = parts.next() {
108 self.write_escaped(std::str::from_utf8(first).map_err(|_| EdifactError::InvalidUtf8)?)?;
111 }
112 for part in parts {
113 self.inner.write_all(&[comp_sep])?;
114 self.write_escaped(std::str::from_utf8(part).map_err(|_| EdifactError::InvalidUtf8)?)?;
115 }
116 }
117 self.inner.write_all(&[self.ssa.segment_term])?;
118 self.segment_count += 1;
119 Ok(())
120 }
121
122 pub fn write_segment_parts<E>(
128 &mut self,
129 tag: &str,
130 elements: &[E],
131 ) -> Result<(), EdifactError>
132 where
133 E: AsRef<[String]>,
134 {
135 self.inner.write_all(tag.as_bytes())?;
136 for element in elements {
137 self.inner.write_all(&[self.ssa.element_sep])?;
138 let mut first = true;
139 for comp in element.as_ref() {
140 if !first {
141 self.inner.write_all(&[self.ssa.component_sep])?;
142 }
143 first = false;
144 self.write_escaped(comp.as_str())?;
145 }
146 }
147 self.inner.write_all(&[self.ssa.segment_term])?;
148 self.segment_count += 1;
149 Ok(())
150 }
151
152 pub fn finish(mut self) -> Result<W, EdifactError> {
154 self.inner.flush()?;
155 Ok(self.inner)
156 }
157
158 pub fn segment_count(&self) -> u32 {
160 self.segment_count
161 }
162
163 #[inline]
167 pub(crate) fn write_tag_only(&mut self, tag: &str) -> Result<(), EdifactError> {
168 self.inner.write_all(tag.as_bytes())?;
169 Ok(())
170 }
171
172 #[inline]
174 pub(crate) fn write_element_sep(&mut self) -> Result<(), EdifactError> {
175 self.inner.write_all(&[self.ssa.element_sep])?;
176 Ok(())
177 }
178
179 #[inline]
181 pub(crate) fn write_component_sep(&mut self) -> Result<(), EdifactError> {
182 self.inner.write_all(&[self.ssa.component_sep])?;
183 Ok(())
184 }
185
186 #[inline]
188 pub(crate) fn write_segment_term_and_count(&mut self) -> Result<(), EdifactError> {
189 self.inner.write_all(&[self.ssa.segment_term])?;
190 self.segment_count += 1;
191 Ok(())
192 }
193
194 pub(crate) fn write_escaped(&mut self, value: &str) -> Result<(), EdifactError> {
196 let (elem, comp, release, term) = (
197 self.ssa.element_sep,
198 self.ssa.component_sep,
199 self.ssa.release_char,
200 self.ssa.segment_term,
201 );
202 let bytes = value.as_bytes();
203 let mut pos = 0;
204 while pos < bytes.len() {
205 let end = bytes[pos..]
207 .iter()
208 .position(|&b| b == elem || b == comp || b == release || b == term)
209 .map(|r| pos + r)
210 .unwrap_or(bytes.len());
211 if end > pos {
212 self.inner.write_all(&bytes[pos..end])?;
213 }
214 if end < bytes.len() {
215 self.inner.write_all(&[release, bytes[end]])?;
216 pos = end + 1;
217 } else {
218 break;
219 }
220 }
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::model::Element;
229
230 #[test]
231 fn write_and_parse_simple_segment() {
232 let segs: Vec<Segment<'static>> = vec![Segment::new(
233 "BGM",
234 vec![Element::of(&["220"]), Element::of(&["ORDER123"])],
235 )];
236 let bytes = crate::segments_to_bytes(&segs).unwrap();
237 let s = std::str::from_utf8(&bytes).unwrap();
238 assert!(s.starts_with("BGM+220+ORDER123'"));
239 }
240
241 #[test]
242 fn release_char_escaped() {
243 let segs: Vec<Segment<'static>> = vec![Segment::new(
244 "FTX",
245 vec![Element::of(&["value+with+delimiters"])],
246 )];
247 let bytes = crate::segments_to_bytes(&segs).unwrap();
248 let s = std::str::from_utf8(&bytes).unwrap();
249 assert!(s.contains("?+"), "escape missing: {s}");
251 }
252
253 #[test]
254 fn round_trip_preserves_values() {
255 let segs: Vec<Segment<'static>> = vec![
256 Segment::new(
257 "UNB",
258 vec![
259 Element::of(&["UNOA", "1"]),
260 Element::of(&["SENDER"]),
261 Element::of(&["RECEIVER"]),
262 ],
263 ),
264 Segment::new("UNZ", vec![Element::of(&["0"]), Element::of(&["1"])]),
265 ];
266 let bytes = crate::segments_to_bytes(&segs).unwrap();
267 let rt: Vec<crate::OwnedSegment> =
268 crate::parser::from_reader(std::io::Cursor::new(&bytes))
269 .expect("round-trip parse failed");
270 assert_eq!(rt[0].tag, "UNB");
271 assert_eq!(rt[0].as_borrowed().element_str(0), Some("UNOA"));
272 assert_eq!(rt[1].tag, "UNZ");
273 }
274
275 #[test]
279 fn with_una_non_default_delimiters() {
280 use crate::tokenizer::ServiceStringAdvice;
281
282 let ssa = ServiceStringAdvice {
284 component_sep: b'|',
285 element_sep: b'!',
286 release_char: b'?',
287 decimal_mark: b',',
288 segment_term: b'~',
289 };
290
291 let buf = Vec::new();
292 let mut writer = Writer::with_una(buf, ssa).expect("writer creation failed");
293
294 writer
296 .write_segment_parts("BGM", &[vec!["220".to_owned(), "SUB1".to_owned()], vec!["PO1".to_owned()]])
297 .expect("write failed");
298
299 let out = writer.finish().expect("finish failed");
300 let s = std::str::from_utf8(&out).unwrap();
301
302 assert!(s.contains("BGM"), "BGM segment missing: {s}");
305 let after_una = s.find("BGM").map(|i| &s[i..]).unwrap_or(s);
307 assert!(after_una.contains('!'), "missing element sep in segment: {after_una}");
308 assert!(after_una.contains('|'), "missing component sep in segment: {after_una}");
309 assert!(after_una.ends_with('~'), "missing segment term in segment: {after_una}");
310 assert!(s.contains(','), "missing decimal mark in UNA: {s}");
312 assert!(!s.contains('+'), "default element sep leaked: {s}");
313 assert!(!s.contains(':'), "default component sep leaked: {s}");
314 assert!(!after_una.contains('\''), "default segment term leaked after UNA: {after_una}");
316 }
317}