Skip to main content

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 const 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 | ScalarValue::Unit => {
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::Char(c) => {
133                let mut buf = [0u8; 4];
134                self.write_csv_escaped(c.encode_utf8(&mut buf));
135            }
136            ScalarValue::I64(v) => {
137                #[cfg(feature = "fast")]
138                self.out
139                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
140                #[cfg(not(feature = "fast"))]
141                self.out.extend_from_slice(v.to_string().as_bytes());
142            }
143            ScalarValue::U64(v) => {
144                #[cfg(feature = "fast")]
145                self.out
146                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
147                #[cfg(not(feature = "fast"))]
148                self.out.extend_from_slice(v.to_string().as_bytes());
149            }
150            ScalarValue::I128(v) => {
151                #[cfg(feature = "fast")]
152                self.out
153                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
154                #[cfg(not(feature = "fast"))]
155                self.out.extend_from_slice(v.to_string().as_bytes());
156            }
157            ScalarValue::U128(v) => {
158                #[cfg(feature = "fast")]
159                self.out
160                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
161                #[cfg(not(feature = "fast"))]
162                self.out.extend_from_slice(v.to_string().as_bytes());
163            }
164            ScalarValue::F64(v) => {
165                #[cfg(feature = "fast")]
166                self.out
167                    .extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
168                #[cfg(not(feature = "fast"))]
169                self.out.extend_from_slice(v.to_string().as_bytes());
170            }
171            ScalarValue::Str(s) => {
172                self.write_csv_escaped(&s);
173            }
174            ScalarValue::Bytes(_) => {
175                return Err(CsvSerializeError {
176                    msg: "CSV does not support binary data",
177                });
178            }
179        }
180        Ok(())
181    }
182}
183
184/// Serialize a value to CSV bytes.
185pub fn to_vec<'facet, T>(value: &T) -> Result<Vec<u8>, SerializeError<CsvSerializeError>>
186where
187    T: Facet<'facet> + ?Sized,
188{
189    let mut serializer = CsvSerializer::new();
190    serialize_root(&mut serializer, Peek::new(value))?;
191    Ok(serializer.finish())
192}
193
194/// Serialize a value to a CSV string.
195pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<CsvSerializeError>>
196where
197    T: Facet<'facet> + ?Sized,
198{
199    let bytes = to_vec(value)?;
200    Ok(String::from_utf8(bytes).expect("CSV output should always be valid UTF-8"))
201}
202
203/// Serialize a value to a writer in CSV format.
204pub fn to_writer<'facet, W, T>(writer: &mut W, value: &T) -> std::io::Result<()>
205where
206    W: std::io::Write,
207    T: Facet<'facet> + ?Sized,
208{
209    let bytes = to_vec(value).map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
210    writer.write_all(&bytes)
211}