use nonempty::NonEmpty;
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
hash::{BuildHasher, Hash},
};
use toml::de::DeValue;
use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
use crate::{DeserializeErrors, DeserializeTable, DeserializeValue, Error, Key};
trait MapFromTable<'de, K, V>
where
K: DeserializeValue<'de>,
V: DeserializeValue<'de>,
{
type Output: Extend<(K, V)>;
fn new_map_from_table() -> Self::Output;
#[allow(clippy::type_complexity, reason = "not complex")]
fn deserialize_table(
table: toml::Spanned<toml::de::DeTable<'de>>,
) -> Result<
Self::Output,
DeserializeErrors<Self::Output, NonEmpty<DeserializeMapError<K::Error, V::Error>>>,
> {
let mut map = Self::new_map_from_table();
let mut errs = vec![];
let span = table.span();
for (toml_key, toml_value) in table.into_inner() {
let key = match K::deserialize_value(
Key::None,
toml::Spanned::new(
toml_key.span(),
DeValue::String(toml_key.clone().into_inner()),
),
) {
Ok(k) => Some(k),
Err(DeserializeErrors { recovered, errors }) => {
errs.extend(errors.into_iter().map(CollectionErrorSingle::Key));
recovered
}
};
let value = match V::deserialize_value(Key::Field(toml_key), toml_value) {
Ok(k) => Some(k),
Err(DeserializeErrors { recovered, errors }) => {
errs.extend(errors.into_iter().map(CollectionErrorSingle::Value));
recovered
}
};
if let (Some(key_val), Some(value_val)) = (key, value) {
map.extend([(key_val, value_val)]);
}
}
NonEmpty::from_vec(errs).map_or_else(
|| Ok(map),
|key_errors| {
Err(DeserializeErrors::err(
DeserializeMapError::CollectionError {
description: "table".to_string(),
span: span.into(),
key_errors,
},
))
},
)
}
}
#[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
pub enum CollectionErrorSingle<K: Error, V: Error> {
#[error(transparent)]
#[diagnostic(transparent)]
Key(K),
#[error(transparent)]
#[diagnostic(transparent)]
Value(V),
}
#[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
#[error("Type mismatch")]
pub enum DeserializeMapError<K: Error, V: Error> {
CollectionError {
description: String,
#[label("{description}")]
span: SourceSpan,
#[related]
key_errors: NonEmpty<CollectionErrorSingle<K, V>>,
},
NotTable {
description: String,
#[label("{description}")]
span: SourceSpan,
},
}
impl<K, V> Error for DeserializeMapError<K, V>
where
K: Error,
V: Error,
{
fn expected_type() -> Cow<'static, str> {
format!(
"map of {{ {} => {} }}",
K::expected_type(),
V::expected_type()
)
.into()
}
}
macro_rules! impl_for {
(
init = $expr:expr,
map = $map:ident<$($generic:ident),*> where $($bounds:tt)*
) => {
impl<'de, $($generic),*> DeserializeTable<'de> for $map<$($generic),*>
where $($bounds)*
{
type Error = DeserializeMapError<K::Error, V::Error>;
fn deserialize_table(
table: toml::Spanned<toml::de::DeTable<'de>>,
) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
<Self as MapFromTable<K, V>>::deserialize_table(table)
}
}
impl<'de, $($generic),*> MapFromTable<'de, K, V> for $map<$($generic),*>
where $($bounds)*
{
type Output = Self;
fn new_map_from_table() -> Self::Output {
$expr
}
}
};
}
impl_for! {
init = Self::new(),
map = BTreeMap<K, V> where
K: DeserializeValue<'de> + Ord,
V: DeserializeValue<'de>
}
impl_for! {
init = Self::with_hasher(S::default()),
map = HashMap<K, V, S> where
K: DeserializeValue<'de> + Hash + Eq,
V: DeserializeValue<'de>,
S: BuildHasher + Default
}