extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use facet_core::Facet;
use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
use facet_reflect::Peek;
#[derive(Debug)]
pub struct CsvSerializeError {
msg: &'static str,
}
impl core::fmt::Display for CsvSerializeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.msg)
}
}
impl std::error::Error for CsvSerializeError {}
pub struct CsvSerializer {
out: Vec<u8>,
in_struct: bool,
first_field: bool,
}
impl CsvSerializer {
pub const fn new() -> Self {
Self {
out: Vec::new(),
in_struct: false,
first_field: true,
}
}
pub fn finish(self) -> Vec<u8> {
self.out
}
fn write_csv_escaped(&mut self, s: &str) {
let needs_quoting =
s.contains(',') || s.contains('"') || s.contains('\n') || s.contains('\r');
if needs_quoting {
self.out.push(b'"');
for c in s.chars() {
if c == '"' {
self.out.extend_from_slice(b"\"\"");
} else {
let mut buf = [0u8; 4];
let len = c.encode_utf8(&mut buf).len();
self.out.extend_from_slice(&buf[..len]);
}
}
self.out.push(b'"');
} else {
self.out.extend_from_slice(s.as_bytes());
}
}
}
impl Default for CsvSerializer {
fn default() -> Self {
Self::new()
}
}
impl FormatSerializer for CsvSerializer {
type Error = CsvSerializeError;
fn begin_struct(&mut self) -> Result<(), Self::Error> {
if self.in_struct {
return Err(CsvSerializeError {
msg: "CSV does not support nested structures",
});
}
self.in_struct = true;
self.first_field = true;
Ok(())
}
fn field_key(&mut self, _key: &str) -> Result<(), Self::Error> {
if !self.first_field {
self.out.push(b',');
}
self.first_field = false;
Ok(())
}
fn end_struct(&mut self) -> Result<(), Self::Error> {
self.in_struct = false;
self.out.push(b'\n');
Ok(())
}
fn begin_seq(&mut self) -> Result<(), Self::Error> {
Err(CsvSerializeError {
msg: "CSV does not support sequences",
})
}
fn end_seq(&mut self) -> Result<(), Self::Error> {
Err(CsvSerializeError {
msg: "CSV does not support sequences",
})
}
fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
match scalar {
ScalarValue::Null | ScalarValue::Unit => {
}
ScalarValue::Bool(v) => {
if v {
self.out.extend_from_slice(b"true");
} else {
self.out.extend_from_slice(b"false");
}
}
ScalarValue::Char(c) => {
let mut buf = [0u8; 4];
self.write_csv_escaped(c.encode_utf8(&mut buf));
}
ScalarValue::I64(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::U64(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::I128(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::U128(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::F64(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::Str(s) => {
self.write_csv_escaped(&s);
}
ScalarValue::Bytes(_) => {
return Err(CsvSerializeError {
msg: "CSV does not support binary data",
});
}
}
Ok(())
}
}
pub fn to_vec<'facet, T>(value: &T) -> Result<Vec<u8>, SerializeError<CsvSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let mut serializer = CsvSerializer::new();
serialize_root(&mut serializer, Peek::new(value))?;
Ok(serializer.finish())
}
pub fn to_string<'facet, T>(value: &T) -> Result<String, SerializeError<CsvSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let bytes = to_vec(value)?;
Ok(String::from_utf8(bytes).expect("CSV output should always be valid UTF-8"))
}
pub fn to_writer<'facet, W, T>(writer: &mut W, value: &T) -> std::io::Result<()>
where
W: std::io::Write,
T: Facet<'facet> + ?Sized,
{
let bytes = to_vec(value).map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
writer.write_all(&bytes)
}