use std::borrow::Cow;
use std::fmt::Write;
use deser_core::ext::ExtValue;
use deser_core::hints::Layout;
use deser_core::ser::{self, SerializeDriver, SerializeRef};
use deser_core::{Atom, BytesFormat, Error, ErrorKind, Event, Serialize, State};
use crate::document::{Document, Entry, Item, Span, TableKind, Value};
use deser_core::ext::{Datetime, Number, Timestamp};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SerializerConfig {
context: deser_core::Context,
}
impl SerializerConfig {
pub const fn new() -> SerializerConfig {
SerializerConfig {
context: deser_core::Context::new(),
}
}
pub const fn builder() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
pub const fn into_builder(self) -> SerializerConfigBuilder {
SerializerConfigBuilder { value: self }
}
pub fn set_context(&mut self, context: deser_core::Context) {
self.context = context;
}
pub fn context(&self) -> &deser_core::Context {
&self.context
}
#[inline]
fn apply_context(&self, driver: &mut SerializeDriver<'_>) {
if !self.context.is_empty() {
driver.set_default_context(self.context.clone());
}
}
pub fn to_string<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, Error> {
self.to_string_ref(SerializeRef::new(&value))
}
pub fn to_string_with<F, T: Serialize + ?Sized>(
&self,
value: &T,
setup: F,
) -> Result<String, Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
let mut driver = SerializeDriver::new(&value);
setup(&mut driver);
self.apply_context(&mut driver);
self.serialize_driver(&mut driver)
}
fn to_string_ref(&self, value: SerializeRef<'_>) -> Result<String, Error> {
let mut driver = SerializeDriver::from_ref(value);
self.apply_context(&mut driver);
self.serialize_driver(&mut driver)
}
pub(crate) fn serialize_driver(
&self,
driver: &mut SerializeDriver<'_>,
) -> Result<String, Error> {
let mut builder = Builder {
doc: Document::default(),
stack: Vec::new(),
done: false,
bytes: BytesFormat::of(driver.state()),
};
driver.drive(|event, state| builder.event(event, state))?;
if !builder.done {
return Err(Error::new(
ErrorKind::InvalidState,
"no value was serialized",
));
}
let mut writer = Writer {
doc: &builder.doc,
out: String::new(),
};
writer.write_document()?;
Ok(writer.out)
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct SerializerConfigBuilder {
value: SerializerConfig,
}
impl SerializerConfigBuilder {
pub const fn new() -> SerializerConfigBuilder {
SerializerConfigBuilder {
value: SerializerConfig::new(),
}
}
pub fn context(mut self, context: deser_core::Context) -> SerializerConfigBuilder {
self.value.set_context(context);
self
}
pub const fn build(self) -> SerializerConfig {
let value = unsafe { core::ptr::read(&self.value) };
core::mem::forget(self);
value
}
}
impl Default for SerializerConfigBuilder {
fn default() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct Serializer {
config: SerializerConfig,
out: String,
written: usize,
}
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 {
config,
out: String::new(),
written: 0,
}
}
pub fn serialize<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
ser::Serializer::serialize(self, value)
}
pub fn serialize_with<F, T: Serialize + ?Sized>(
&mut self,
value: &T,
setup: F,
) -> Result<(), Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
ser::Serializer::serialize_with(self, value, setup)
}
pub fn config(&self) -> &SerializerConfig {
&self.config
}
pub fn as_str(&self) -> &str {
&self.out
}
pub fn finish(self) -> String {
self.out
}
}
impl ser::Serializer for Serializer {
fn drive(&mut self, driver: &mut SerializeDriver<'_>) -> Result<(), Error> {
if !self.config.context.is_empty() {
driver.set_default_context(self.config.context.clone());
}
if self.written > 0 {
return Err(Error::new(
ErrorKind::InvalidState,
"a TOML document holds a single value",
));
}
let toml = self.config.serialize_driver(driver)?;
self.out.push_small(&toml);
self.written += 1;
Ok(())
}
}
impl ser::StreamSerializer for Serializer {
fn output(&self) -> &[u8] {
self.out.as_bytes()
}
fn clear_output(&mut self) {
self.out.clear();
}
}
#[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.clone()))
}
pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
&self,
writer: W,
value: &T,
) -> Result<(), Error> {
self.writer(writer).write(value)
}
}
#[cfg(feature = "io")]
pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
writer: W,
value: &T,
) -> Result<(), Error> {
SerializerConfig::new().to_writer(writer, value)
}
pub fn to_string<T: Serialize + ?Sized>(value: &T) -> Result<String, Error> {
SerializerConfig::new().to_string(value)
}
enum Frame {
Table(usize, Option<String>),
Array(usize),
}
struct Builder {
doc: Document<'static>,
stack: Vec<Frame>,
done: bool,
bytes: BytesFormat,
}
enum Converted {
Value(Value<'static>),
Null,
}
impl Builder {
fn event(&mut self, event: Event, state: &State) -> Result<(), Error> {
let Some(frame) = self.stack.last_mut() else {
if self.done {
return Err(Error::new(ErrorKind::InvalidState, "unexpected event"));
}
return match event {
Event::MapStart(_) => {
let id = self.doc.new_table(TableKind::Header, Span::default());
self.stack.push(Frame::Table(id, None));
Ok(())
}
_ => Err(Error::new(
ErrorKind::UnsupportedType,
"TOML documents must be tables",
)),
};
};
match *frame {
Frame::Table(_, ref mut key @ None) => match event {
Event::Atom(atom) => {
*key = Some(key_to_string(atom, self.bytes)?);
Ok(())
}
Event::MapEnd => {
self.stack.pop();
self.done = self.stack.is_empty();
Ok(())
}
_ => Err(unsupported_key()),
},
Frame::Table(id, ref mut key @ Some(_)) => {
let key = key.take().unwrap();
let value = match self.value(event, state)? {
Converted::Value(value) => value,
Converted::Null => return Ok(()),
};
let entry = Entry {
key: Cow::Owned(key),
key_span: Span::default(),
item: Item {
value,
span: Span::default(),
},
};
match self.doc.insert_new(id, entry) {
Ok(()) => Ok(()),
Err(entry) => Err(Error::new(
ErrorKind::DuplicateKey,
format!("duplicate key `{}`", entry.key),
)),
}
}
Frame::Array(id) => {
if event == Event::SeqEnd {
self.stack.pop();
return Ok(());
}
match self.value(event, state)? {
Converted::Value(value) => {
self.doc.arrays[id].items.push(Item {
value,
span: Span::default(),
});
Ok(())
}
Converted::Null => Err(Error::new(
ErrorKind::UnsupportedType,
"TOML does not support null values in arrays",
)),
}
}
}
}
fn value(&mut self, event: Event, state: &State) -> Result<Converted, Error> {
match event {
Event::Atom(Atom::Bytes(ref bytes)) => Ok(Converted::Value(
self.bytes_value(bytes, bytes.fallback.copied().unwrap_or(self.bytes)),
)),
Event::Atom(atom) => convert_atom(atom, self.bytes),
Event::MapStart(_) => {
let kind = match Layout::of(state) {
Layout::Compact => TableKind::Inline,
_ => TableKind::Header,
};
let id = self.doc.new_table(kind, Span::default());
self.stack.push(Frame::Table(id, None));
Ok(Converted::Value(Value::Table(id)))
}
Event::SeqStart(_) => {
let of_tables = Layout::of(state) != Layout::Compact;
let id = self.doc.new_array(of_tables, Span::default());
self.stack.push(Frame::Array(id));
Ok(Converted::Value(Value::Array(id)))
}
Event::MapEnd | Event::SeqEnd => {
Err(Error::new(ErrorKind::InvalidState, "unexpected end event"))
}
}
}
fn bytes_value(&mut self, bytes: &[u8], format: BytesFormat) -> Value<'static> {
match format.encode(bytes) {
Some(encoded) => Value::Str(Cow::Owned(encoded)),
None => {
let id = self.doc.new_array(false, Span::default());
self.doc.arrays[id]
.items
.extend(bytes.iter().map(|&byte| Item {
value: Value::Int(byte.into()),
span: Span::default(),
}));
Value::Array(id)
}
}
}
}
fn convert_atom(atom: Atom, bytes: BytesFormat) -> Result<Converted, Error> {
Ok(Converted::Value(match atom {
Atom::Null => return Ok(Converted::Null),
Atom::Bool(value) => Value::Bool(value),
Atom::Str(value) | Atom::Lexical(value) => Value::Str(Cow::Owned(value.into_owned())),
Atom::Char(value) => Value::Str(Cow::Owned(value.to_string())),
Atom::U64(value) => match i64::try_from(value) {
Ok(value) => Value::Int(value),
Err(_) => Value::UInt(value),
},
Atom::I64(value) => Value::Int(value),
Atom::F64(value) => Value::Float(value),
Atom::F32(value) => Value::Float32(value),
Atom::Bytes(value) => Value::Str(Cow::Owned(encode_str(&value, value.fallback, bytes))),
Atom::Ext(ref ext) => return convert_ext(ext, bytes),
Atom::Implicit(value) => return convert_atom(value.value().to_atom(), bytes),
_ => return Err(Error::new(ErrorKind::UnsupportedType, "unknown atom")),
}))
}
#[cold]
fn convert_ext(ext: &ExtValue, bytes: BytesFormat) -> Result<Converted, Error> {
if let Some(value) = ext.downcast_ref::<Datetime>() {
if !value.is_valid() {
return Err(Error::new(ErrorKind::InvalidValue, "invalid datetime"));
}
return Ok(Converted::Value(Value::Datetime(*value)));
}
if let Some(value) = ext.downcast_value_ref::<Number>() {
return Ok(Converted::Value(if value.is_integer() {
Value::Float(value.value())
} else {
Value::FloatText(Cow::Owned(value.as_str().to_string()))
}));
}
if let Some(value) = ext
.downcast_ref::<Timestamp>()
.and_then(|x| x.to_datetime())
{
return Ok(Converted::Value(Value::Datetime(value)));
}
let out_of_range = || Error::new(ErrorKind::OutOfRange, "integer out of range for TOML");
if let Some(&value) = ext.downcast_ref::<u128>() {
let value = u64::try_from(value).map_err(|_| out_of_range())?;
return convert_atom(Atom::U64(value), bytes);
}
if let Some(&value) = ext.downcast_ref::<i128>() {
return if let Ok(value) = i64::try_from(value) {
convert_atom(Atom::I64(value), bytes)
} else {
let value = u64::try_from(value).map_err(|_| out_of_range())?;
convert_atom(Atom::U64(value), bytes)
};
}
match ext.fallback() {
Atom::Ext(_) => Err(Error::new(
ErrorKind::UnsupportedType,
format!("TOML does not support {}", ext.name()),
)),
fallback => convert_atom(fallback, bytes),
}
}
fn encode_str(value: &[u8], fallback: Option<&BytesFormat>, bytes: BytesFormat) -> String {
fallback
.copied()
.unwrap_or(bytes)
.encode(value)
.or_else(|| BytesFormat::BASE64.encode(value))
.unwrap_or_default()
}
fn key_to_string(atom: Atom, bytes: BytesFormat) -> Result<String, Error> {
Ok(match atom {
Atom::Implicit(value) => return key_to_string(value.value().to_atom(), bytes),
Atom::Str(value) | Atom::Lexical(value) => value.into_owned(),
Atom::Char(value) => value.to_string(),
Atom::U64(value) => value.to_string(),
Atom::I64(value) => value.to_string(),
Atom::Bool(value) => value.to_string(),
Atom::Bytes(value) => encode_str(&value, value.fallback, bytes),
Atom::Ext(ref ext) => {
if let Some(value) = ext.downcast_ref::<u128>() {
value.to_string()
} else if let Some(value) = ext.downcast_ref::<i128>() {
value.to_string()
} else {
match ext.fallback() {
Atom::Ext(_) => return Err(unsupported_key()),
fallback => return key_to_string(fallback, bytes),
}
}
}
_ => return Err(unsupported_key()),
})
}
#[cold]
fn unsupported_key() -> Error {
Error::new(
ErrorKind::UnsupportedType,
"TOML only supports strings, integers and booleans as keys",
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Header {
None,
Table,
ArrayTable,
}
struct Section<'d> {
id: usize,
path: Vec<&'d str>,
header: Header,
}
enum InlineFrame {
Table(usize, usize),
Array(usize, usize),
}
struct Writer<'d> {
doc: &'d Document<'static>,
out: String,
}
impl<'d> Writer<'d> {
fn is_section(&self, value: &Value) -> bool {
match *value {
Value::Table(id) => self.doc.tables[id].kind != TableKind::Inline,
Value::Array(id) => self.is_array_of_tables(id),
_ => false,
}
}
fn is_array_of_tables(&self, id: usize) -> bool {
let array = &self.doc.arrays[id];
array.of_tables
&& !array.items.is_empty()
&& array
.items
.iter()
.all(|x| matches!(x.value, Value::Table(_)))
}
fn write_document(&mut self) -> Result<(), Error> {
let doc = self.doc;
let mut sections = vec![Section {
id: 0,
path: Vec::new(),
header: Header::None,
}];
while let Some(section) = sections.pop() {
let table = &doc.tables[section.id];
let has_values = table
.entries
.iter()
.any(|x| !self.is_section(&x.item.value));
let header = match section.header {
Header::Table if !has_values && !table.entries.is_empty() => None,
Header::Table => Some(("[", "]")),
Header::ArrayTable => Some(("[[", "]]")),
Header::None => None,
};
if let Some((open, close)) = header {
if !self.out.is_empty() {
self.out.push('\n');
}
self.out.push_small(open);
for (idx, key) in section.path.iter().enumerate() {
if idx > 0 {
self.out.push('.');
}
write_key(&mut self.out, key);
}
self.out.push_small(close);
self.out.push('\n');
}
for entry in &table.entries {
if !self.is_section(&entry.item.value) {
write_key(&mut self.out, &entry.key);
self.out.push_small(" = ");
self.write_value(&entry.item.value)?;
self.out.push('\n');
}
}
let first_child = sections.len();
for entry in &table.entries {
let child_path = || {
let mut path = section.path.clone();
path.push(&*entry.key);
path
};
match entry.item.value {
Value::Table(id) if self.is_section(&entry.item.value) => {
sections.push(Section {
id,
path: child_path(),
header: Header::Table,
})
}
Value::Array(id) if self.is_array_of_tables(id) => {
for item in &doc.arrays[id].items {
if let Value::Table(id) = item.value {
sections.push(Section {
id,
path: child_path(),
header: Header::ArrayTable,
});
}
}
}
_ => {}
}
}
sections[first_child..].reverse();
}
Ok(())
}
fn write_value(&mut self, value: &Value) -> Result<(), Error> {
let doc = self.doc;
let mut stack = Vec::new();
self.write_value_start(value, &mut stack);
while let Some(frame) = stack.last_mut() {
let value = match *frame {
InlineFrame::Table(id, ref mut index) => {
let table = &doc.tables[id];
match table.entries.get(*index) {
Some(entry) => {
self.out.push_small(if *index == 0 { " " } else { ", " });
*index += 1;
write_key(&mut self.out, &entry.key);
self.out.push_small(" = ");
&entry.item.value
}
None => {
self.out
.push_small(if table.entries.is_empty() { "}" } else { " }" });
stack.pop();
continue;
}
}
}
InlineFrame::Array(id, ref mut index) => match doc.arrays[id].items.get(*index) {
Some(item) => {
if *index > 0 {
self.out.push_small(", ");
}
*index += 1;
&item.value
}
None => {
self.out.push(']');
stack.pop();
continue;
}
},
};
self.write_value_start(value, &mut stack);
}
Ok(())
}
fn write_value_start(&mut self, value: &Value, stack: &mut Vec<InlineFrame>) {
match *value {
Value::Str(ref value) => write_string(&mut self.out, value),
Value::Int(value) => self.out.push_small(itoa::Buffer::new().format(value)),
Value::UInt(value) => self.out.push_small(itoa::Buffer::new().format(value)),
Value::Float(value) => write_float(&mut self.out, value),
Value::Float32(value) => write_float(&mut self.out, value),
Value::FloatText(ref value) => self.out.push_small(value),
Value::Bool(value) => self.out.push_small(if value { "true" } else { "false" }),
Value::Datetime(ref value) => write!(self.out, "{}", value).unwrap(),
Value::Table(id) => {
self.out.push('{');
stack.push(InlineFrame::Table(id, 0));
}
Value::Array(id) => {
self.out.push('[');
stack.push(InlineFrame::Array(id, 0));
}
}
}
}
fn write_float<F: zmij::Float + Into<f64>>(out: &mut String, value: F) {
let wide: f64 = value.into();
if wide.is_nan() {
out.push_small("nan");
} else if wide.is_infinite() {
out.push_small(if wide > 0.0 { "inf" } else { "-inf" });
} else {
out.push_small(zmij::Buffer::new().format_finite(value));
}
}
fn is_bare_key(key: &str) -> bool {
!key.is_empty()
&& key
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'-')
}
fn write_key(out: &mut String, key: &str) {
if is_bare_key(key) {
out.push_small(key);
} else {
write_basic_string(out, key);
}
}
fn write_string(out: &mut String, value: &str) {
if value.contains('\n') {
write_multiline_string(out, value);
} else if value.contains(['"', '\\'])
&& !value
.chars()
.any(|c| c == '\'' || (c.is_control() && c != '\t'))
{
out.push('\'');
out.push_small(value);
out.push('\'');
} else {
write_basic_string(out, value);
}
}
fn write_control_escape(out: &mut String, c: char) {
match c {
'\x08' => out.push_small("\\b"),
'\t' => out.push_small("\\t"),
'\n' => out.push_small("\\n"),
'\x0c' => out.push_small("\\f"),
'\r' => out.push_small("\\r"),
c => write!(out, "\\u{:04X}", c as u32).unwrap(),
}
}
fn is_escaped_control(c: char) -> bool {
matches!(c, '\0'..='\x1f' | '\x7f')
}
fn write_basic_string(out: &mut String, value: &str) {
out.push('"');
for c in value.chars() {
match c {
'"' => out.push_small("\\\""),
'\\' => out.push_small("\\\\"),
c if is_escaped_control(c) => write_control_escape(out, c),
c => out.push(c),
}
}
out.push('"');
}
fn write_multiline_string(out: &mut String, value: &str) {
out.push_small("\"\"\"\n");
let mut quotes = 0;
for c in value.chars() {
match c {
'"' if quotes == 2 => {
out.push_small("\\\"");
quotes = 0;
continue;
}
'"' => out.push('"'),
'\\' => out.push_small("\\\\"),
'\n' | '\t' => out.push(c),
c if is_escaped_control(c) => write_control_escape(out, c),
c => out.push(c),
}
quotes = if c == '"' { quotes + 1 } else { 0 };
}
out.push_small("\"\"\"");
}
trait PushSmall {
fn push_small(&mut self, s: &str);
}
impl PushSmall for String {
#[inline(always)]
fn push_small(&mut self, s: &str) {
crate::copy::push_str(self, s);
}
}