Skip to main content

edifact_rs/
ser.rs

1//! Custom serialization trait for EDIFACT.
2//!
3//! [`EdifactSerialize`] emits typed EDIFACT events rather than the generic
4//! key/value tokens of standard `serde`.  This matches EDIFACT's positional,
5//! qualifier-based data model — see `SPIKE_NOTES.md` for the design rationale.
6
7use crate::EdifactError;
8use crate::event::{EdifactEvent, EventEmitter, WriterEmitter};
9use std::io::Write;
10
11// ── trait ─────────────────────────────────────────────────────────────────────
12
13/// Types that can serialize themselves to an EDIFACT event stream.
14///
15/// Implement manually or derive with `#[derive(EdifactSerialize)]` from the
16/// `edifact-rs-derive` crate.
17pub trait EdifactSerialize {
18    /// Serialize `self` by emitting events into `emitter`.
19    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError>;
20}
21
22/// Types that can serialize themselves as a composite EDIFACT element.
23///
24/// Implement this for custom composite structs used with
25/// `#[edifact(composite)]` in derive macros.
26pub trait EdifactCompositeSerialize {
27    /// Serialize `self` as one composite element into `emitter`.
28    fn edifact_serialize_composite<E: EventEmitter>(
29        &self,
30        emitter: &mut E,
31    ) -> Result<(), EdifactError>;
32}
33
34impl EdifactCompositeSerialize for Vec<String> {
35    fn edifact_serialize_composite<E: EventEmitter>(
36        &self,
37        emitter: &mut E,
38    ) -> Result<(), EdifactError> {
39        if self.is_empty() {
40            return emitter.emit(EdifactEvent::Element { value: "" });
41        }
42
43        emitter.emit(EdifactEvent::Element { value: &self[0] })?;
44        for component in self.iter().skip(1) {
45            emitter.emit(EdifactEvent::ComponentElement { value: component })?;
46        }
47        Ok(())
48    }
49}
50
51// ── blanket impls for scalar types ────────────────────────────────────────────
52
53impl EdifactSerialize for str {
54    #[inline]
55    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
56        emitter.emit(EdifactEvent::Element { value: self })
57    }
58}
59
60impl EdifactSerialize for String {
61    #[inline]
62    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
63        emitter.emit(EdifactEvent::Element {
64            value: self.as_str(),
65        })
66    }
67}
68
69/// `None` → empty element `""`; `Some(v)` → `v.edifact_serialize(emitter)`.
70impl<T: EdifactSerialize> EdifactSerialize for Option<T> {
71    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
72        match self {
73            Some(v) => v.edifact_serialize(emitter),
74            None => emitter.emit(EdifactEvent::Element { value: "" }),
75        }
76    }
77}
78
79/// Each element is serialized independently (repeated segments for groups).
80impl<T: EdifactSerialize> EdifactSerialize for Vec<T> {
81    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
82        for item in self {
83            item.edifact_serialize(emitter)?;
84        }
85        Ok(())
86    }
87}
88
89/// Each element is serialized independently (repeated segments for groups).
90impl<T: EdifactSerialize> EdifactSerialize for [T] {
91    fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
92        for item in self {
93            item.edifact_serialize(emitter)?;
94        }
95        Ok(())
96    }
97}
98
99macro_rules! impl_serialize_int {
100    ($($t:ty),+ $(,)?) => {
101        $(
102            impl EdifactSerialize for $t {
103                fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
104                    // Stack buffer: 40 bytes covers i128::MIN
105                    // ("-170141183460469231731687303715884105728") exactly.
106                    // Integer Display lengths are bounded; floats must NOT use this path.
107                    let mut buf = [0u8; 40];
108                    let s = {
109                        use std::io::Write as _;
110                        let mut cursor = std::io::Cursor::new(&mut buf[..]);
111                        // SAFETY: integer Display is bounded; buf is large enough.
112                        write!(cursor, "{}", self).expect("integer format into fixed buffer");
113                        let len = cursor.position() as usize;
114                        std::str::from_utf8(&buf[..len]).expect("integer output is valid UTF-8")
115                    };
116                    emitter.emit(EdifactEvent::Element { value: s })
117                }
118            }
119        )+
120    };
121}
122
123// Boolean is also bounded (max "false" = 5 bytes).
124impl_serialize_int!(
125    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool
126);
127
128// Rust's float Display picks the shortest round-trip decimal representation,
129// which may be fixed-point or scientific notation depending on the magnitude.
130// A 320-byte stack buffer is a conservative upper bound for all finite f32
131// and f64 values, avoiding heap allocation.
132//
133// NOTE: Rust's Display always uses `.` as the decimal separator. The decimal
134// mark configured in `ServiceStringAdvice.decimal_mark` (UNA byte 5) is NOT
135// respected by these impls. If your interchange declares a different decimal
136// mark (e.g. `,`), wrap the value in a newtype whose `EdifactSerialize` impl
137// formats with the correct separator.
138macro_rules! impl_serialize_float {
139    ($($t:ty),+ $(,)?) => {
140        $(
141            impl EdifactSerialize for $t {
142                fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
143                    use std::io::Write as _;
144                    let mut buf = [0u8; 320];
145                    let written = {
146                        let mut w: &mut [u8] = &mut buf;
147                        write!(w, "{self}").expect("float Display exceeded 320-byte stack buffer");
148                        320 - w.len()
149                    };
150                    let s = std::str::from_utf8(&buf[..written])
151                        .expect("float Display is always ASCII");
152                    emitter.emit(EdifactEvent::Element { value: s })
153                }
154            }
155        )+
156    };
157}
158
159impl_serialize_float!(f32, f64);
160
161// ── public API ────────────────────────────────────────────────────────────────
162
163/// Serialize `value` to the given [`Write`] implementation.
164pub fn to_writer<T, W>(inner: W, value: &T) -> Result<(), EdifactError>
165where
166    T: EdifactSerialize,
167    W: Write,
168{
169    let mut emitter = WriterEmitter::new(inner);
170    value.edifact_serialize(&mut emitter)?;
171    emitter.finish().map(|_| ())
172}
173
174/// Serialize `value` to an owned `Vec<u8>`.
175pub fn to_bytes<T: EdifactSerialize>(value: &T) -> Result<Vec<u8>, EdifactError> {
176    let mut buf = Vec::new();
177    to_writer(&mut buf, value)?;
178    Ok(buf)
179}
180
181/// Serialize `value` to a UTF-8 `String`.
182///
183/// # Allocations
184///
185/// Allocates one `Vec<u8>` via [`to_bytes`].  The subsequent conversion to
186/// `String` reuses that allocation in-place via [`String::from_utf8`] — no
187/// second allocation occurs.  When you only need raw bytes (e.g. for a network
188/// write), prefer [`to_bytes`] directly.
189pub fn to_edifact_string<T: EdifactSerialize>(value: &T) -> Result<String, EdifactError> {
190    let bytes = to_bytes(value)?;
191    String::from_utf8(bytes).map_err(|_| EdifactError::InvalidUtf8)
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::event::{OwnedEdifactEvent, VecEmitter};
198
199    struct BgmSegment {
200        doc_name_code: String,
201        pruef_id: String,
202        msg_function: Option<String>,
203    }
204
205    impl EdifactSerialize for BgmSegment {
206        fn edifact_serialize<E: EventEmitter>(&self, emitter: &mut E) -> Result<(), EdifactError> {
207            emitter.emit(EdifactEvent::StartSegment { tag: "BGM" })?;
208            emitter.emit(EdifactEvent::Element {
209                value: &self.doc_name_code,
210            })?;
211            emitter.emit(EdifactEvent::Element {
212                value: &self.pruef_id,
213            })?;
214            self.msg_function.edifact_serialize(emitter)?;
215            emitter.emit(EdifactEvent::EndSegment)?;
216            Ok(())
217        }
218    }
219
220    #[test]
221    fn vec_emitter_captures_segment_events() {
222        let seg = BgmSegment {
223            doc_name_code: "E03".to_owned(),
224            pruef_id: "11042".to_owned(),
225            msg_function: None,
226        };
227        let mut emitter = VecEmitter::default();
228        seg.edifact_serialize(&mut emitter).unwrap();
229
230        assert_eq!(
231            emitter.events[0],
232            OwnedEdifactEvent::StartSegment {
233                tag: "BGM".to_owned()
234            }
235        );
236        assert_eq!(emitter.events.last(), Some(&OwnedEdifactEvent::EndSegment));
237    }
238
239    #[test]
240    fn to_bytes_produces_valid_edifact() {
241        let seg = BgmSegment {
242            doc_name_code: "E03".to_owned(),
243            pruef_id: "11042".to_owned(),
244            msg_function: Some("9".to_owned()),
245        };
246        let bytes = to_bytes(&seg).unwrap();
247        assert_eq!(std::str::from_utf8(&bytes).unwrap(), "BGM+E03+11042+9'");
248    }
249
250    #[test]
251    fn option_none_emits_empty_element() {
252        let val: Option<String> = None;
253        let mut emitter = VecEmitter::default();
254        val.edifact_serialize(&mut emitter).unwrap();
255        assert_eq!(
256            emitter.events[0],
257            OwnedEdifactEvent::Element {
258                value: String::new()
259            }
260        );
261    }
262
263    #[test]
264    fn option_some_emits_value() {
265        let val: Option<String> = Some("TEST".to_owned());
266        let mut emitter = VecEmitter::default();
267        val.edifact_serialize(&mut emitter).unwrap();
268        assert_eq!(
269            emitter.events[0],
270            OwnedEdifactEvent::Element {
271                value: "TEST".to_owned()
272            }
273        );
274    }
275
276    #[test]
277    fn integer_types_serialize_without_alloc() {
278        let mut emitter = VecEmitter::default();
279        42u32.edifact_serialize(&mut emitter).unwrap();
280        assert_eq!(
281            emitter.events[0],
282            OwnedEdifactEvent::Element {
283                value: "42".to_owned()
284            }
285        );
286        // i128::MIN should fit exactly in the 40-byte buffer
287        let mut emitter2 = VecEmitter::default();
288        i128::MIN.edifact_serialize(&mut emitter2).unwrap();
289        assert_eq!(
290            emitter2.events[0],
291            OwnedEdifactEvent::Element {
292                value: "-170141183460469231731687303715884105728".to_owned()
293            }
294        );
295    }
296
297    #[test]
298    fn float_extremes_do_not_panic() {
299        // Rust Display for f64 picks the shortest round-trip form; a 320-byte buffer covers all values.
300        let mut emitter = VecEmitter::default();
301        f64::MAX.edifact_serialize(&mut emitter).unwrap();
302        let s = match &emitter.events[0] {
303            OwnedEdifactEvent::Element { value } => value.clone(),
304            _ => panic!("expected Element event"),
305        };
306        assert!(!s.is_empty());
307        // f32::MAX too
308        let mut emitter2 = VecEmitter::default();
309        f32::MAX.edifact_serialize(&mut emitter2).unwrap();
310        assert!(matches!(&emitter2.events[0], OwnedEdifactEvent::Element { .. }));
311    }
312
313    #[test]
314    fn vec_serializes_each_item() {
315        let segments = vec![
316            BgmSegment {
317                doc_name_code: "E03".to_owned(),
318                pruef_id: "11042".to_owned(),
319                msg_function: None,
320            },
321            BgmSegment {
322                doc_name_code: "E01".to_owned(),
323                pruef_id: "11043".to_owned(),
324                msg_function: None,
325            },
326        ];
327        let bytes = to_bytes(&segments).unwrap();
328        let s = std::str::from_utf8(&bytes).unwrap();
329        assert!(s.contains("BGM+E03+11042"));
330        assert!(s.contains("BGM+E01+11043"));
331    }
332}