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