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 const 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 | ScalarValue::Unit => {
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::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
184pub 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
194pub 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
203pub 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}