1extern 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#[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
26pub struct CsvSerializer {
28 out: Vec<u8>,
29 in_struct: bool,
30 first_field: bool,
31}
32
33impl CsvSerializer {
34 pub fn new() -> Self {
36 Self {
37 out: Vec::new(),
38 in_struct: false,
39 first_field: true,
40 }
41 }
42
43 pub fn finish(self) -> Vec<u8> {
45 self.out
46 }
47
48 fn write_csv_escaped(&mut self, s: &str) {
49 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 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 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 }
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
180pub 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
190pub 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
199pub 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}