oh_my_toml 0.1.0

Awesome TOML configuration derive macro
Documentation
//! Utilities

use crate::error::DeserializeSequenceError;
use crate::{DeserializeErrors, Key, Value};
use std::borrow::Cow;

use nonempty::NonEmpty;
use toml::de::DeValue;

use crate::Error;

/// Describes the [`Value`] as a string
pub fn value_type_description(value: &Value) -> Cow<'static, str> {
    let value = value.as_ref();
    if let Some(bool) = value.as_bool() {
        bool.to_string().into()
    } else if value.is_str() {
        "string".into()
    } else if value.is_integer() {
        "int".into()
    } else if value.is_float() {
        "float".into()
    } else if let Some(array) = value.as_array() {
        let are_all_types_eq = array.iter().all(|value| {
            Some(value_type_description(value)) == array.iter().next().map(value_type_description)
        });

        if are_all_types_eq && let Some(first) = array.iter().next() {
            format!("list of {}", value_type_description(first)).into()
        } else if array.is_empty() {
            "empty list".into()
        } else {
            format!(
                "tuple ({})",
                array
                    .iter()
                    .map(value_type_description)
                    .collect::<Vec<_>>()
                    .join(", ")
            )
            .into()
        }
    } else if value.is_table() {
        "table".into()
    } else if value.is_datetime() {
        "date".into()
    } else {
        "unknown".into()
    }
}

/// Deserialize a [`toml_edit::Value`] into a sequence of `T`s,
/// capturing the errors we found along the way
#[allow(clippy::missing_errors_doc, reason = "explain later")]
#[allow(clippy::type_complexity, reason = "it is not complex")]
pub fn deserialize_sequence<'de, T, E: Error, Deserializer, Describer>(
    value: Value<'de>,
    deserializer: Deserializer,
    describer: Describer,
) -> Result<Vec<T>, (Option<Vec<T>>, DeserializeSequenceError<E>)>
where
    Deserializer: for<'a> Fn(Key<'a>, Value<'de>) -> Result<T, DeserializeErrors<T, NonEmpty<E>>>,
    Describer: for<'a> Fn(&'a Value) -> String,
{
    let span = value.span();
    let details = describer(&value);

    let DeValue::Array(array) = value.into_inner() else {
        return Err((
            None,
            DeserializeSequenceError::NotList {
                details,
                span: span.into(),
            },
        ));
    };

    let mut errs = vec![];
    let mut ts = vec![];

    for (i, value) in array.into_iter().enumerate() {
        match deserializer(Key::Index(i), value) {
            Ok(t) => {
                ts.push(t);
            }
            Err(DeserializeErrors { recovered, errors }) => {
                ts.extend(recovered);
                errs.extend(errors);
            }
        }
    }

    if let Some(errors) = NonEmpty::from_vec(errs) {
        Err((
            Some(ts),
            DeserializeSequenceError::InvalidSequence {
                details,
                span: span.into(),
                errors,
            },
        ))
    } else {
        Ok(ts)
    }
}