use alloc::format;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::{self, Write as _};
use deser_core::__format::{Float, IntBuffer, format_finite};
use deser_core::ext::Number;
use deser_core::ser::{self, PausableSink, SerializeDriver, Written};
use deser_core::{Atom, BytesFormat, Error, ErrorKind, Event, Serialize, State};
use crate::parser::Dialect;
use crate::{Escape, Nulls, QuoteStyle, Terminator};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SerializerConfig {
delimiter: u8,
quote: Option<u8>,
double_quote: bool,
escape: Escape,
terminator: Terminator,
quote_style: QuoteStyle,
headers: bool,
columns: Option<&'static [&'static str]>,
nulls: Nulls,
flexible: bool,
escape_formulas: bool,
bytes: BytesFormat,
}
impl Default for SerializerConfig {
fn default() -> SerializerConfig {
SerializerConfig::new()
}
}
impl SerializerConfig {
pub const fn new() -> SerializerConfig {
SerializerConfig {
delimiter: b',',
quote: Some(b'"'),
double_quote: true,
escape: Escape::None,
terminator: Terminator::Newline,
quote_style: QuoteStyle::Necessary,
headers: true,
columns: None,
nulls: Nulls::None,
flexible: false,
escape_formulas: false,
bytes: BytesFormat::BASE64,
}
}
pub const fn tsv() -> SerializerConfig {
SerializerConfig::new()
.delimiter(b'\t')
.quote(None)
.escape(Escape::Backslash)
.nulls(Nulls::Text("\\N"))
}
pub const fn delimiter(mut self, delimiter: u8) -> SerializerConfig {
self.delimiter = delimiter;
self
}
pub const fn quote(mut self, quote: Option<u8>) -> SerializerConfig {
self.quote = quote;
self
}
pub const fn double_quote(mut self, yes: bool) -> SerializerConfig {
self.double_quote = yes;
self
}
pub const fn escape(mut self, escape: Escape) -> SerializerConfig {
self.escape = escape;
self
}
pub const fn terminator(mut self, terminator: Terminator) -> SerializerConfig {
self.terminator = terminator;
self
}
pub const fn quote_style(mut self, style: QuoteStyle) -> SerializerConfig {
self.quote_style = style;
self
}
pub const fn headers(mut self, yes: bool) -> SerializerConfig {
self.headers = yes;
self
}
pub const fn columns(mut self, names: &'static [&'static str]) -> SerializerConfig {
self.columns = Some(names);
self
}
pub const fn nulls(mut self, nulls: Nulls) -> SerializerConfig {
self.nulls = nulls;
self
}
pub const fn flexible(mut self, yes: bool) -> SerializerConfig {
self.flexible = yes;
self
}
pub const fn escape_formulas(mut self, yes: bool) -> SerializerConfig {
self.escape_formulas = yes;
self
}
pub const fn bytes(mut self, format: BytesFormat) -> SerializerConfig {
self.bytes = format;
self
}
pub fn to_string(&self, value: &dyn Serialize) -> Result<String, Error> {
self.to_string_with(value, |_| {})
}
pub fn to_string_with<F>(&self, value: &dyn Serialize, setup: F) -> Result<String, Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
let mut driver = SerializeDriver::new(value);
setup(&mut driver);
let mut out = Vec::new();
self.write_whole(&mut WriterState::default(), &mut driver, true, &mut out)?;
Ok(into_string(out))
}
pub(crate) fn write(
&self,
state: &mut WriterState,
driver: &mut SerializeDriver<'_>,
document: bool,
out: &mut Vec<u8>,
limit: usize,
) -> Result<bool, Error> {
let drive: DriveFn = if limit == usize::MAX {
drive_whole
} else {
drive_partial
};
self.write_with(state, driver, document, out, limit, drive)
}
pub(crate) fn write_whole(
&self,
state: &mut WriterState,
driver: &mut SerializeDriver<'_>,
document: bool,
out: &mut Vec<u8>,
) -> Result<(), Error> {
self.write_with(state, driver, document, out, usize::MAX, drive_whole)
.map(|_| ())
}
fn write_with(
&self,
state: &mut WriterState,
driver: &mut SerializeDriver<'_>,
document: bool,
out: &mut Vec<u8>,
limit: usize,
drive: DriveFn,
) -> Result<bool, Error> {
let dialect = match state.dialect {
Some(ref dialect) => dialect,
None => state.dialect.insert(Dialect::new(
self.delimiter,
self.quote,
self.double_quote,
self.escape,
self.terminator,
None,
)?),
};
let mut writer = RecordWriter {
encoder: FieldEncoder {
config: self,
dialect,
},
names: state.names.take(),
len: state.len,
document,
direct: false,
is_map: false,
fields: 0,
record_start: 0,
field_ends: core::mem::take(&mut state.buffers.field_ends),
record: core::mem::take(&mut state.buffers.record),
scratch: core::mem::take(&mut state.buffers.scratch),
open: false,
limit,
out,
};
let had_names = writer.names.is_some();
let rv = drive(driver, &mut writer);
if rv.is_ok() || had_names {
state.names = writer.names;
}
if rv.is_ok() {
state.len = writer.len;
}
state.buffers = Buffers {
field_ends: writer.field_ends,
record: writer.record,
scratch: writer.scratch,
};
rv
}
}
type DriveFn = fn(&mut SerializeDriver<'_>, &mut RecordWriter<'_>) -> Result<bool, Error>;
fn drive_whole(
driver: &mut SerializeDriver<'_>,
writer: &mut RecordWriter<'_>,
) -> Result<bool, Error> {
driver
.drive(|event, state| writer.event(event, state))
.map(|()| true)
}
fn drive_partial(
driver: &mut SerializeDriver<'_>,
writer: &mut RecordWriter<'_>,
) -> Result<bool, Error> {
driver.drive_until(writer)
}
#[derive(Clone, Default)]
struct Buffers {
field_ends: Vec<usize>,
record: Record,
scratch: Vec<u8>,
}
impl core::fmt::Debug for Buffers {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Buffers").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct WriterState {
names: Option<Vec<String>>,
len: Option<usize>,
dialect: Option<Dialect>,
buffers: Buffers,
}
impl WriterState {
fn with_headers(names: Vec<String>) -> WriterState {
WriterState {
len: Some(names.len()),
names: Some(names),
dialect: None,
buffers: Buffers::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct Serializer {
config: SerializerConfig,
state: WriterState,
out: Vec<u8>,
document: bool,
in_progress: bool,
}
impl Default for Serializer {
fn default() -> Serializer {
Serializer::new()
}
}
impl Serializer {
pub fn new() -> Serializer {
Serializer::with_config(&SerializerConfig::new())
}
pub fn with_config(config: &SerializerConfig) -> Serializer {
Serializer::with_state(config, WriterState::default(), false)
}
pub fn with_headers<I, S>(config: &SerializerConfig, names: I) -> Serializer
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let names = names.into_iter().map(Into::into).collect();
Serializer::with_state(config, WriterState::with_headers(names), false)
}
pub fn document(config: &SerializerConfig) -> Serializer {
Serializer::with_state(config, WriterState::default(), true)
}
fn with_state(config: &SerializerConfig, state: WriterState, document: bool) -> Serializer {
Serializer {
config: config.clone(),
state,
out: Vec::new(),
document,
in_progress: false,
}
}
pub fn config(&self) -> &SerializerConfig {
&self.config
}
pub fn headers(&self) -> Option<&[String]> {
self.state.names.as_deref()
}
pub fn serialize(&mut self, value: &dyn Serialize) -> Result<(), Error> {
ser::Serializer::serialize(self, value)
}
pub fn serialize_with<F>(&mut self, value: &dyn Serialize, setup: F) -> Result<(), Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
ser::Serializer::serialize_with(self, value, setup)
}
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.out) }
}
pub fn finish(self) -> String {
into_string(self.out)
}
}
impl ser::Serializer for Serializer {
fn drive(&mut self, driver: &mut SerializeDriver<'_>) -> Result<(), Error> {
if self.in_progress {
return Err(Error::in_progress());
}
let len = self.out.len();
match self
.config
.write_whole(&mut self.state, driver, self.document, &mut self.out)
{
Ok(()) => Ok(()),
Err(err) => {
self.out.truncate(len);
Err(err)
}
}
}
}
impl ser::StreamSerializer for Serializer {
fn output(&self) -> &[u8] {
&self.out
}
fn clear_output(&mut self) {
self.out.clear();
}
fn supports_partial(&self) -> bool {
self.document
}
fn drive_partial(
&mut self,
driver: &mut SerializeDriver<'_>,
limit: usize,
) -> Result<Written, Error> {
if !self.document || (limit == usize::MAX && !self.in_progress) {
ser::Serializer::drive(self, driver)?;
return Ok(Written::Done);
}
let len = self.out.len();
match self
.config
.write(&mut self.state, driver, true, &mut self.out, limit)
{
Ok(true) => {
self.in_progress = false;
Ok(Written::Done)
}
Ok(false) => {
self.in_progress = true;
Ok(Written::Partial)
}
Err(err) => {
self.out.truncate(len);
Err(err)
}
}
}
fn in_progress(&self) -> bool {
self.in_progress
}
}
#[cfg(feature = "io")]
impl SerializerConfig {
pub fn writer<W: std::io::Write>(&self, writer: W) -> deser_core::io::Writer<W, Serializer> {
deser_core::io::Writer::new(writer, Serializer::with_config(self))
}
pub fn to_writer<W: std::io::Write>(
&self,
writer: W,
value: &dyn Serialize,
) -> Result<(), Error> {
deser_core::io::to_writer(writer, Serializer::document(self), value)
}
}
#[cfg(feature = "io")]
pub fn to_writer<W: std::io::Write>(writer: W, value: &dyn Serialize) -> Result<(), Error> {
SerializerConfig::new().to_writer(writer, value)
}
fn into_string(out: Vec<u8>) -> String {
debug_assert!(core::str::from_utf8(&out).is_ok());
unsafe { String::from_utf8_unchecked(out) }
}
pub fn to_string(value: &dyn Serialize) -> Result<String, Error> {
SerializerConfig::new().to_string(value)
}
#[derive(Clone, Default)]
struct Record {
keys: Vec<u8>,
key_ends: Vec<usize>,
fields: Vec<u8>,
field_ends: Vec<usize>,
}
impl Record {
fn clear(&mut self) {
self.keys.clear();
self.key_ends.clear();
self.fields.clear();
self.field_ends.clear();
}
fn key(&self, index: usize) -> &[u8] {
let start = if index == 0 {
0
} else {
self.key_ends[index - 1]
};
&self.keys[start..self.key_ends[index]]
}
fn field(&self, index: usize) -> &[u8] {
let start = if index == 0 {
0
} else {
self.field_ends[index - 1]
};
&self.fields[start..self.field_ends[index]]
}
}
struct RecordWriter<'a> {
encoder: FieldEncoder<'a>,
names: Option<Vec<String>>,
len: Option<usize>,
document: bool,
direct: bool,
is_map: bool,
fields: usize,
record_start: usize,
field_ends: Vec<usize>,
record: Record,
scratch: Vec<u8>,
open: bool,
limit: usize,
out: &'a mut Vec<u8>,
}
impl PausableSink for RecordWriter<'_> {
#[inline]
fn event(
&mut self,
event: Event<'_>,
_value: &dyn Serialize,
state: &mut State,
) -> Result<(), Error> {
RecordWriter::event(self, event, state)
}
#[inline]
fn pause(&mut self) -> bool {
!self.open && self.out.len() >= self.limit
}
}
impl RecordWriter<'_> {
#[inline]
fn event(&mut self, event: Event<'_>, state: &State) -> Result<(), Error> {
if let Event::Atom(ref atom) = event
&& state.depth() == usize::from(self.document) + 1
{
return self.atom(atom, state.is_map_key());
}
self.structure(event, state)
}
fn structure(&mut self, event: Event<'_>, state: &State) -> Result<(), Error> {
let depth = match event {
Event::MapStart(_) | Event::SeqStart(_) => state.depth().saturating_sub(1),
_ => state.depth(),
};
let record_depth = usize::from(self.document);
match event {
Event::SeqStart(_) | Event::SeqEnd if self.document && depth == 0 => {}
_ if depth < record_depth => {
return Err(Error::new(
ErrorKind::UnsupportedType,
"CSV documents are sequences of records",
));
}
Event::MapStart(_) | Event::SeqStart(_) if depth == record_depth => {
if self.names.is_none()
&& let Some(columns) = self.encoder.config.columns
{
let names: Vec<String> = columns.iter().map(|name| name.to_string()).collect();
if self.encoder.config.headers {
self.write_names(&names)?;
}
self.names = Some(names);
}
self.is_map = matches!(event, Event::MapStart(_));
self.open = true;
self.direct = !self.is_map || self.names.is_some();
self.fields = 0;
self.record_start = self.out.len();
self.field_ends.clear();
self.record.clear();
}
Event::MapEnd | Event::SeqEnd if depth == record_depth => {
self.open = false;
self.finish_record()?
}
_ if depth == record_depth => {
return Err(Error::new(
ErrorKind::UnsupportedType,
"CSV records must be maps or sequences",
));
}
Event::MapStart(_) | Event::SeqStart(_) | Event::MapEnd | Event::SeqEnd => {
return Err(Error::new(
ErrorKind::UnsupportedType,
"CSV fields cannot hold maps or sequences",
));
}
Event::Atom(ref atom) => return self.atom(atom, state.is_map_key()),
}
Ok(())
}
#[inline]
fn atom(&mut self, atom: &Atom<'_>, is_key: bool) -> Result<(), Error> {
if is_key {
return self.key(atom);
}
let text = self.encoder.text(atom, &mut self.scratch)?;
if self.direct {
if self.fields > 0 {
self.out.push(self.encoder.dialect.delimiter);
}
self.encoder.encode(text, self.out)?;
self.field_ends.push(self.out.len());
} else {
self.encoder.encode(text, &mut self.record.fields)?;
self.record.field_ends.push(self.record.fields.len());
}
self.fields += 1;
Ok(())
}
fn key(&mut self, atom: &Atom<'_>) -> Result<(), Error> {
let key = match atom {
Atom::Null | Atom::Bytes(_) => None,
atom => self.encoder.text(atom, &mut self.scratch)?,
};
let key = key.ok_or_else(unsupported_key)?;
if !self.direct {
self.record.keys.extend_from_slice(key.bytes);
} else {
let names = self.names.as_deref().unwrap_or_default();
if names.get(self.fields).map(String::as_bytes) == Some(key.bytes) {
return Ok(());
}
let key = key.bytes.to_vec();
self.collect();
self.record.keys.extend_from_slice(&key);
}
self.record.key_ends.push(self.record.keys.len());
Ok(())
}
fn collect(&mut self) {
let names = self.names.as_deref().unwrap_or_default();
let mut start = self.record_start;
for (index, &end) in self.field_ends.iter().enumerate() {
if index > 0 {
start += 1;
}
self.record.fields.extend_from_slice(&self.out[start..end]);
self.record.field_ends.push(self.record.fields.len());
self.record.keys.extend_from_slice(names[index].as_bytes());
self.record.key_ends.push(self.record.keys.len());
start = end;
}
self.out.truncate(self.record_start);
self.direct = false;
}
fn finish_record(&mut self) -> Result<(), Error> {
let start = self.record_start;
if !self.is_map {
self.check_len(self.fields)?;
return self.terminate(self.fields, start);
}
if self.direct {
let len = self.names.as_ref().map_or(0, Vec::len);
for column in self.fields..len {
if column > 0 {
self.out.push(self.encoder.dialect.delimiter);
}
self.encoder.encode(None, self.out)?;
}
self.check_len(len)?;
return self.terminate(len, start);
}
let count = self.record.field_ends.len();
if self.names.is_none() {
let names: Vec<String> = (0..count)
.map(|index| String::from_utf8_lossy(self.record.key(index)).into_owned())
.collect();
if self.encoder.config.headers {
self.write_names(&names)?;
}
self.names = Some(names);
}
let len = self.names.as_ref().map_or(0, Vec::len);
self.check_len(len)?;
let names = self.names.as_ref().unwrap();
let start = self.out.len();
let mut order = vec![None; len];
for index in 0..count {
let key = String::from_utf8_lossy(self.record.key(index));
match names.iter().position(|name| *name == key) {
Some(column) if order[column].is_none() => order[column] = Some(index),
Some(_) => {
return Err(Error::new(
ErrorKind::Unexpected,
format!("duplicate field `{}`", key),
));
}
None => {
return Err(Error::new(
ErrorKind::Unexpected,
format!("field `{}` is not a column", key),
));
}
}
}
for (column, index) in order.into_iter().enumerate() {
if column > 0 {
self.out.push(self.encoder.dialect.delimiter);
}
match index {
Some(index) => self.out.extend_from_slice(self.record.field(index)),
None => self.encoder.encode(None, self.out)?,
}
}
self.terminate(len, start)
}
fn check_len(&mut self, len: usize) -> Result<(), Error> {
match self.len {
Some(expected) if expected != len && !self.encoder.config.flexible => Err(Error::new(
ErrorKind::Unexpected,
format!("record has {} fields, expected {}", len, expected),
)),
Some(_) => Ok(()),
None => {
self.len = Some(len);
Ok(())
}
}
}
fn terminate(&mut self, len: usize, start: usize) -> Result<(), Error> {
if len == 1 && self.out.len() == start {
match self.encoder.dialect.quote {
Some(quote) => self.out.extend_from_slice(&[quote, quote]),
None => {
return Err(Error::new(
ErrorKind::Unexpected,
"a record with a single empty field needs quotes",
));
}
}
}
match self.encoder.config.terminator {
Terminator::Newline => self.out.push(b'\n'),
Terminator::CrLf => self.out.extend_from_slice(b"\r\n"),
Terminator::Byte(byte) => self.out.push(byte),
}
Ok(())
}
fn write_names(&mut self, names: &[String]) -> Result<(), Error> {
let start = self.out.len();
for (index, name) in names.iter().enumerate() {
if index > 0 {
self.out.push(self.encoder.dialect.delimiter);
}
let text = Text {
bytes: name.as_bytes(),
numeric: false,
};
self.encoder.encode(Some(text), self.out)?;
}
self.terminate(names.len(), start)
}
}
#[derive(Clone, Copy)]
struct Text<'a> {
bytes: &'a [u8],
numeric: bool,
}
#[derive(Clone, Copy)]
struct FieldEncoder<'a> {
config: &'a SerializerConfig,
dialect: &'a Dialect,
}
impl FieldEncoder<'_> {
fn encode(&self, text: Option<Text<'_>>, out: &mut Vec<u8>) -> Result<(), Error> {
let config = self.config;
let Some(Text { bytes, numeric }) = text else {
if let Nulls::Text(null) = config.nulls {
out.extend_from_slice(null.as_bytes());
}
return Ok(());
};
let reads_as_null = match config.nulls {
Nulls::None => false,
Nulls::Empty => bytes.is_empty(),
Nulls::Text(null) => bytes == null.as_bytes(),
};
let formula = config.escape_formulas
&& !numeric
&& matches!(
bytes.first(),
Some(b'=' | b'+' | b'-' | b'@' | b'\t' | b'\r')
);
let has_special = self.dialect.has_special(bytes);
let quote_style = match config.quote_style {
QuoteStyle::Always => true,
QuoteStyle::NonNumeric => !numeric,
QuoteStyle::Necessary | QuoteStyle::Never => false,
};
if !quote_style && !has_special && !reads_as_null && !formula {
out.extend_from_slice(bytes);
return Ok(());
}
let prefix: &[u8] = if formula { b"'" } else { b"" };
let escape = config.escape.byte();
let prefix_is_special = formula && self.dialect.is_special(b'\'');
let quoted = quote_style
|| (formula && self.dialect.quote.is_some() && config.quote_style != QuoteStyle::Never)
|| (escape.is_none() && (has_special || prefix_is_special || reads_as_null))
|| (reads_as_null && bytes.is_empty());
let text = prefix.iter().chain(bytes).copied();
if !quoted {
for (index, byte) in text.enumerate() {
if self.dialect.is_special(byte) || (index == 0 && reads_as_null) {
out.push(escape.unwrap_or(b'\\'));
out.push(self.escaped(byte));
} else {
out.push(byte);
}
}
return Ok(());
}
let quote = match self.dialect.quote {
Some(quote) if config.quote_style != QuoteStyle::Never => quote,
_ => {
return Err(Error::new(
ErrorKind::Unexpected,
format!(
"field {:?} needs to be quoted",
String::from_utf8_lossy(bytes)
),
));
}
};
out.push(quote);
if formula {
self.push_quoted(b'\'', quote, out)?;
}
let mut bytes = bytes;
loop {
let run = bytes
.iter()
.position(|&b| b == quote || Some(b) == escape)
.unwrap_or(bytes.len());
out.extend_from_slice(&bytes[..run]);
let Some((&byte, rest)) = bytes[run..].split_first() else {
break;
};
bytes = rest;
self.push_quoted(byte, quote, out)?;
}
out.push(quote);
Ok(())
}
fn push_quoted(&self, byte: u8, quote: u8, out: &mut Vec<u8>) -> Result<(), Error> {
let escape = self.config.escape.byte();
if byte == quote && self.config.double_quote {
out.extend_from_slice(&[quote, quote]);
} else if byte == quote || Some(byte) == escape {
match escape {
Some(escape) => out.extend_from_slice(&[escape, self.escaped(byte)]),
None => {
return Err(Error::new(
ErrorKind::Unexpected,
"quotes in quoted fields need to be doubled or escaped",
));
}
}
} else {
out.push(byte);
}
Ok(())
}
fn escaped(&self, byte: u8) -> u8 {
match (self.config.escape, byte) {
(Escape::Backslash, b'\t') => b't',
(Escape::Backslash, b'\n') => b'n',
(Escape::Backslash, b'\r') => b'r',
(_, byte) => byte,
}
}
fn text<'a>(
&self,
atom: &'a Atom<'_>,
scratch: &'a mut Vec<u8>,
) -> Result<Option<Text<'a>>, Error> {
scratch.clear();
let numeric = match *atom {
Atom::Null => return Ok(None),
Atom::Bool(value) => {
let text: &[u8] = if value { b"true" } else { b"false" };
return Ok(Some(Text {
bytes: text,
numeric: false,
}));
}
Atom::Str(ref value) | Atom::Lexical(ref value) => {
return Ok(Some(Text {
bytes: value.as_bytes(),
numeric: false,
}));
}
Atom::Char(value) => {
scratch.extend_from_slice(value.encode_utf8(&mut [0; 4]).as_bytes());
false
}
Atom::U64(value) => {
scratch.extend_from_slice(IntBuffer::new().format_u64(value).as_bytes());
true
}
Atom::I64(value) => {
scratch.extend_from_slice(IntBuffer::new().format_i64(value).as_bytes());
true
}
Atom::F32(value) => {
write_float(scratch, value);
true
}
Atom::F64(value) => {
write_float(scratch, value);
true
}
Atom::Bytes(ref bytes) => {
let format = bytes.fallback.copied().unwrap_or(self.config.bytes);
let text = format
.encode(bytes)
.or_else(|| BytesFormat::BASE64.encode(bytes))
.unwrap_or_default();
scratch.extend_from_slice(text.as_bytes());
false
}
Atom::Ext(ref ext) => {
if let Some(number) = ext.downcast_value_ref::<Number>() {
scratch.extend_from_slice(number.as_str().as_bytes());
} else if let Some(value) = ext.downcast_ref::<u128>() {
let _ = write!(ByteWriter(scratch), "{}", value);
} else if let Some(value) = ext.downcast_ref::<i128>() {
let _ = write!(ByteWriter(scratch), "{}", value);
} else {
return match ext.fallback() {
Atom::Ext(_) => Err(Error::new(
ErrorKind::UnsupportedType,
format!("CSV does not support {}", ext.name()),
)),
fallback => {
let mut inner = Vec::new();
let numeric = match self.text(&fallback, &mut inner)? {
Some(text) => {
let numeric = text.numeric;
scratch.extend_from_slice(text.bytes);
numeric
}
None => return Ok(None),
};
Ok(Some(Text {
bytes: scratch,
numeric,
}))
}
};
}
true
}
Atom::Implicit(ref value) => {
let mut inner = Vec::new();
return Ok(match self.text(&value.value().to_atom(), &mut inner)? {
Some(text) => {
let numeric = text.numeric;
scratch.extend_from_slice(text.bytes);
Some(Text {
bytes: scratch,
numeric,
})
}
None => None,
});
}
_ => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("CSV does not support {}", atom.name()),
));
}
};
Ok(Some(Text {
bytes: scratch,
numeric,
}))
}
}
struct ByteWriter<'a>(&'a mut Vec<u8>);
impl fmt::Write for ByteWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.extend_from_slice(s.as_bytes());
Ok(())
}
}
fn write_float<F: Float>(out: &mut Vec<u8>, value: F) {
if value.is_finite() {
out.extend_from_slice(format_finite(value).as_bytes());
} else {
let _ = write!(ByteWriter(out), "{}", value.to_f64());
}
}
#[cold]
fn unsupported_key() -> Error {
Error::new(
ErrorKind::UnsupportedType,
"the keys of records must be strings, numbers or booleans",
)
}