facet_csv/
serializer.rs

1//! CSV serialization implementation using FormatSerializer trait.
2
3extern crate alloc;
4
5use alloc::string::String;
6use alloc::vec::Vec;
7
8use facet_core::Facet;
9use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
10use facet_reflect::Peek;
11
12/// Error type for CSV serialization.
13#[derive(Debug)]
14pub struct CsvSerializeError {
15    msg: &'static str,
16}
17
18impl core::fmt::Display for CsvSerializeError {
19    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20        f.write_str(self.msg)
21    }
22}
23
24impl std::error::Error for CsvSerializeError {}
25
26/// CSV serializer implementing FormatSerializer.
27pub struct CsvSerializer {
28    out: Vec<u8>,
29    in_struct: bool,
30    first_field: bool,
31}
32
33impl CsvSerializer {
34    /// Create a new CSV serializer.
35    pub fn new() -> Self {
36        Self {
37            out: Vec::new(),
38            in_struct: false,
39            first_field: true,
40        }
41    }
42
43    /// Consume the serializer and return the output bytes.
44    pub fn finish(self) -> Vec<u8> {
45        self.out
46    }
47
48    fn write_csv_escaped(&mut self, s: &str) {
49        // Check if we need to quote the field
50        let needs_quoting =
51            s.contains(',') || s.contains('"') || s.contains('\n') || s.contains('\r');
52
53        if needs_quoting {
54            self.out.push(b'"');
55            for c in s.chars() {
56                if c == '"' {
57                    self.out.extend_from_slice(b"\"\"");
58                } else {
59                    let mut buf = [0u8; 4];
60                    let len = c.encode_utf8(&mut buf).len();
61                    self.out.extend_from_slice(&buf[..len]);
62                }
63            }
64            self.out.push(b'"');
65        } else {
66            self.out.extend_from_slice(s.as_bytes());
67        }
68    }
69}
70
71impl Default for CsvSerializer {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl FormatSerializer for CsvSerializer {
78    type Error = CsvSerializeError;
79
80    fn begin_struct(&mut self) -> Result<(), Self::Error> {
81        if self.in_struct {
82            return Err(CsvSerializeError {
83                msg: "CSV does not support nested structures",
84            });
85        }
86        self.in_struct = true;
87        self.first_field = true;
88        Ok(())
89    }
90
91    fn field_key(&mut self, _key: &str) -> Result<(), Self::Error> {
92        // CSV doesn't output field names, just values
93        // But we need to add comma separators between fields
94        if !self.first_field {
95            self.out.push(b',');
96        }
97        self.first_field = false;
98        Ok(())
99    }
100
101    fn end_struct(&mut self) -> Result<(), Self::Error> {
102        self.in_struct = false;
103        // Add newline at end of row
104        self.out.push(b'\n');
105        Ok(())
106    }
107
108    fn begin_seq(&mut self) -> Result<(), Self::Error> {
109        Err(CsvSerializeError {
110            msg: "CSV does not support sequences",
111        })
112    }
113
114    fn end_seq(&mut self) -> Result<(), Self::Error> {
115        Err(CsvSerializeError {
116            msg: "CSV does not support sequences",
117        })
118    }
119
120    fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
121        match scalar {
122            ScalarValue::Null => {
123                // Empty field for null
124            }
125            ScalarValue::Bool(v) => {
126                if v {
127                    self.out.extend_from_slice(b"true");
128                } else {
129                    self.out.extend_from_slice(b"false");
130                }
131            }
132            ScalarValue::I64(v) => {
133                #[cfg(feature = "fast")]
134                self.out
135                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
136                #[cfg(not(feature = "fast"))]
137                self.out.extend_from_slice(v.to_string().as_bytes());
138            }
139            ScalarValue::U64(v) => {
140                #[cfg(feature = "fast")]
141                self.out
142                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
143                #[cfg(not(feature = "fast"))]
144                self.out.extend_from_slice(v.to_string().as_bytes());
145            }
146            ScalarValue::I128(v) => {
147                #[cfg(feature = "fast")]
148                self.out
149                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
150                #[cfg(not(feature = "fast"))]
151                self.out.extend_from_slice(v.to_string().as_bytes());
152            }
153            ScalarValue::U128(v) => {
154                #[cfg(feature = "fast")]
155                self.out
156                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
157                #[cfg(not(feature = "fast"))]
158                self.out.extend_from_slice(v.to_string().as_bytes());
159            }
160            ScalarValue::F64(v) => {
161                #[cfg(feature = "fast")]
162                self.out
163                    .extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
164                #[cfg(not(feature = "fast"))]
165                self.out.extend_from_slice(v.to_string().as_bytes());
166            }
167            ScalarValue::Str(s) => {
168                self.write_csv_escaped(&s);
169            }
170            ScalarValue::Bytes(_) => {
171                return Err(CsvSerializeError {
172                    msg: "CSV does not support binary data",
173                });
174            }
175        }
176        Ok(())
177    }
178}
179
180/// Serialize a value to CSV bytes.
181pub fn to_vec<'facet, T>(value: &T) -> Result<Vec<u8>, SerializeError<CsvSerializeError>>
182where
183    T: Facet<'facet> + ?Sized,
184{
185    let mut serializer = CsvSerializer::new();
186    serialize_root(&mut serializer, Peek::new(value))?;
187    Ok(serializer.finish())
188}
189
190/// Serialize a value to a CSV string.
191pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<CsvSerializeError>>
192where
193    T: Facet<'facet> + ?Sized,
194{
195    let bytes = to_vec(value)?;
196    Ok(String::from_utf8(bytes).expect("CSV output should always be valid UTF-8"))
197}
198
199/// Serialize a value to a writer in CSV format.
200pub fn to_writer<'facet, W, T>(writer: &mut W, value: &T) -> std::io::Result<()>
201where
202    W: std::io::Write,
203    T: Facet<'facet> + ?Sized,
204{
205    let bytes = to_vec(value).map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
206    writer.write_all(&bytes)
207}