use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap},
fmt::{self, Debug, Display},
rc::Rc,
sync::Arc,
};
use crate::{Arena, Array, DateTime, Item, Key, Table, item::Value};
fn optional_to_required<'a>(
optional: Result<Option<Item<'a>>, ToTomlError>,
) -> Result<Item<'a>, ToTomlError> {
match optional {
Ok(Some(item)) => Ok(item),
Ok(None) => Err(ToTomlError::from("required value was None")),
Err(e) => Err(e),
}
}
fn required_to_optional<'a>(
required: Result<Item<'a>, ToTomlError>,
) -> Result<Option<Item<'a>>, ToTomlError> {
match required {
Ok(item) => Ok(Some(item)),
Err(e) => Err(e),
}
}
pub trait ToToml {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
optional_to_required(self.to_optional_toml(arena))
}
fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
required_to_optional(self.to_toml(arena))
}
}
impl<K: ToToml> ToToml for BTreeSet<K> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
return length_of_array_exceeded_maximum();
};
for item in self {
array.push(item.to_toml(arena)?, arena);
}
Ok(array.into_item())
}
}
impl<K: ToToml, H> ToToml for std::collections::HashSet<K, H> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
return length_of_array_exceeded_maximum();
};
for item in self {
array.push(item.to_toml(arena)?, arena);
}
Ok(array.into_item())
}
}
impl<const N: usize, T: ToToml> ToToml for [T; N] {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
self.as_slice().to_toml(arena)
}
}
macro_rules! impl_to_toml_tuple {
($len:expr, $($idx:tt => $T:ident),+) => {
impl<$($T: ToToml),+> ToToml for ($($T,)+) {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut array) = Array::try_with_capacity($len, arena) else {
return length_of_array_exceeded_maximum();
};
$(
array.push(self.$idx.to_toml(arena)?, arena);
)+
Ok(array.into_item())
}
}
};
}
impl_to_toml_tuple!(1, 0 => A);
impl_to_toml_tuple!(2, 0 => A, 1 => B);
impl_to_toml_tuple!(3, 0 => A, 1 => B, 2 => C);
impl<T: ToToml> ToToml for Option<T> {
fn to_optional_toml<'a>(&'a self, arena: &'a Arena) -> Result<Option<Item<'a>>, ToTomlError> {
match self {
Some(value) => value.to_optional_toml(arena),
None => Ok(None),
}
}
}
impl ToToml for str {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::string(self))
}
}
impl ToToml for String {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::string(self))
}
}
impl<T: ToToml> ToToml for Box<T> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl<T: ToToml> ToToml for [T] {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut array) = Array::try_with_capacity(self.len(), arena) else {
return length_of_array_exceeded_maximum();
};
for item in self {
array.push(item.to_toml(arena)?, arena);
}
Ok(array.into_item())
}
}
impl<T: ToToml> ToToml for Vec<T> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
self.as_slice().to_toml(arena)
}
}
impl ToToml for f32 {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::from(*self as f64))
}
}
impl ToToml for f64 {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::from(*self))
}
}
impl ToToml for bool {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::from(*self))
}
}
impl ToToml for DateTime {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::from(*self))
}
}
impl<T: ToToml + ?Sized> ToToml for &T {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl<T: ToToml + ?Sized> ToToml for &mut T {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl<T: ToToml> ToToml for Rc<T> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl<T: ToToml> ToToml for Arc<T> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl<'b, T: ToToml + Clone> ToToml for Cow<'b, T> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
<T as ToToml>::to_toml(self, arena)
}
}
impl ToToml for char {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let mut buf = [0; 4];
Ok(Item::string(arena.alloc_str(self.encode_utf8(&mut buf))))
}
}
impl ToToml for std::path::Path {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
match self.to_str() {
Some(s) => Ok(Item::string(s)),
None => ToTomlError::msg("path contains invalid UTF-8 characters"),
}
}
}
impl ToToml for std::path::PathBuf {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
self.as_path().to_toml(arena)
}
}
impl ToToml for Array<'_> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(self.clone_in(arena).into_item())
}
}
impl ToToml for Table<'_> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(self.clone_in(arena).into_item())
}
}
impl ToToml for Item<'_> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(self.clone_in(arena))
}
}
macro_rules! direct_upcast_integers {
($($tt:tt),*) => {
$(impl ToToml for $tt {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
Ok(Item::from(*self as i128))
}
})*
};
}
direct_upcast_integers!(u8, i8, i16, u16, i32, u32, i64, u64, i128);
impl ToToml for u128 {
fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError> {
if *self > i128::MAX as u128 {
return ToTomlError::msg("u128 value exceeds i128::MAX");
}
Ok(Item::from(*self as i128))
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `ToFlattened`",
note = "if `{Self}` implements `ToToml`, you can use `#[toml(flatten, with = flatten_any)]` instead of a manual `ToFlattened` impl"
)]
pub trait ToFlattened {
fn to_flattened<'a>(
&'a self,
arena: &'a Arena,
table: &mut Table<'a>,
) -> Result<(), ToTomlError>;
}
fn key_to_str<'a>(item: &Item<'a>) -> Option<&'a str> {
match item.value() {
Value::String(s) => Some(*s),
_ => None,
}
}
impl<K: ToToml, V: ToToml> ToFlattened for BTreeMap<K, V> {
fn to_flattened<'a>(
&'a self,
arena: &'a Arena,
table: &mut Table<'a>,
) -> Result<(), ToTomlError> {
for (k, v) in self {
let key_item = k.to_toml(arena)?;
let Some(key_str) = key_to_str(&key_item) else {
return map_key_did_not_serialize_to_string();
};
table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
}
Ok(())
}
}
impl<K: ToToml, V: ToToml, H> ToFlattened for HashMap<K, V, H> {
fn to_flattened<'a>(
&'a self,
arena: &'a Arena,
table: &mut Table<'a>,
) -> Result<(), ToTomlError> {
for (k, v) in self {
let key_item = k.to_toml(arena)?;
let Some(key_str) = key_to_str(&key_item) else {
return map_key_did_not_serialize_to_string();
};
table.insert_unique(Key::new(key_str), v.to_toml(arena)?, arena);
}
Ok(())
}
}
impl ToFlattened for Table<'_> {
fn to_flattened<'a>(
&'a self,
arena: &'a Arena,
table: &mut Table<'a>,
) -> Result<(), ToTomlError> {
for (key, val) in self {
table.insert_unique(*key, val.clone_in(arena), arena);
}
Ok(())
}
}
impl ToFlattened for Item<'_> {
fn to_flattened<'a>(
&'a self,
arena: &'a Arena,
table: &mut Table<'a>,
) -> Result<(), ToTomlError> {
let Some(src) = self.as_table() else {
return Err(ToTomlError::from("flatten: expected a table"));
};
src.to_flattened(arena, table)
}
}
impl<K: ToToml, V: ToToml> ToToml for BTreeMap<K, V> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
return length_of_table_exceeded_maximum();
};
self.to_flattened(arena, &mut table)?;
Ok(table.into_item())
}
}
impl<K: ToToml, V: ToToml, H> ToToml for HashMap<K, V, H> {
fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
let Some(mut table) = Table::try_with_capacity(self.len(), arena) else {
return length_of_table_exceeded_maximum();
};
self.to_flattened(arena, &mut table)?;
Ok(table.into_item())
}
}
#[cold]
fn map_key_did_not_serialize_to_string() -> Result<(), ToTomlError> {
Err(ToTomlError::from("map key did not serialize to a string"))
}
#[cold]
fn length_of_array_exceeded_maximum<T>() -> Result<T, ToTomlError> {
Err(ToTomlError::from(
"length of array exceeded maximum capacity",
))
}
#[cold]
fn length_of_table_exceeded_maximum<T>() -> Result<T, ToTomlError> {
Err(ToTomlError::from(
"length of table exceeded maximum capacity",
))
}
pub struct ToTomlError {
pub message: Cow<'static, str>,
}
impl ToTomlError {
#[cold]
pub fn msg<T>(msg: &'static str) -> Result<T, Self> {
Err(Self {
message: Cow::Borrowed(msg),
})
}
}
impl Display for ToTomlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl Debug for ToTomlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ToTomlError")
.field("message", &self.message)
.finish()
}
}
impl std::error::Error for ToTomlError {}
impl From<Cow<'static, str>> for ToTomlError {
fn from(message: Cow<'static, str>) -> Self {
Self { message }
}
}
impl From<&'static str> for ToTomlError {
fn from(message: &'static str) -> Self {
Self {
message: Cow::Borrowed(message),
}
}
}