oh_my_toml 0.1.0

Awesome TOML configuration derive macro
Documentation
//! Deserialize into `[T; N]`

use crate::util::deserialize_sequence;
use crate::{DeserializeErrors, util::value_type_description};

use nonempty::NonEmpty;
use std::borrow::Cow;

use miette::{Diagnostic, SourceSpan};
use thiserror::Error;

use crate::{DeserializeValue, Error, Key, Value};

use super::sequence::DeserializeSequenceError;

// TODO: Figure out how to have a single variant for `LengthMismatch` and `LengthMismatchAndCollectionError`

/// Failed to deserialize `[T; N]`
#[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
pub enum DeserializeArrayError<E: Error, const N: usize> {
    /// Wraps a general `DeserializeCollectionError` for cases where the TOML value
    /// is not an array or has unrecoverable element-level errors.
    #[error(transparent)]
    #[diagnostic(transparent)]
    CollectionError(DeserializeSequenceError<E>),
    /// Represents an error where the deserialized array has a different number of elements
    /// than expected for the fixed-size array `[T; N]`.
    #[error("Array length mismatch")]
    #[diagnostic(help("this array must contain exactly {N} elements."))]
    LengthMismatch {
        /// How many elements we got
        actual_length: usize,
        /// Location of the error in the source file
        #[label("this array has {actual_length} elements")]
        span: SourceSpan,
        /// Inner errors from individual element deserialization, if any occurred
        /// before the length mismatch was detected.
        #[related]
        inner_errors: Vec<E>,
    },
    /// Represents an error where the deserialized array has a different number of elements
    /// than expected for the fixed-size array `[T; N]`.
    #[error("Array length mismatch")]
    #[diagnostic(help("this array must contain exactly {N} elements."))]
    LengthMismatchAndCollectionError {
        /// How many elements we got
        actual_length: usize,
        /// Location of the error in the source file
        #[label("this array has {actual_length} elements")]
        span: SourceSpan,
        /// Inner errors from individual element deserialization, if any occurred
        /// before the length mismatch was detected.
        #[diagnostic(transparent)]
        inner_errors: DeserializeSequenceError<E>,
    },
}

impl<E: Error, const N: usize> Error for DeserializeArrayError<E, N> {
    fn expected_type() -> Cow<'static, str> {
        format!("array of {N} {}", E::expected_type()).into()
    }
}

impl<'de, const N: usize, T: DeserializeValue<'de>> DeserializeValue<'de> for [T; N] {
    type Error = DeserializeArrayError<T::Error, N>;

    fn deserialize_value<'k>(
        _key: Key<'k>,
        value: Value<'de>,
    ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
        let span = value.span();
        match deserialize_sequence(value, T::deserialize_value, |value| {
            DeserializeArrayError::<T::Error, N>::details(value_type_description(value))
        }) {
            Ok(seq) => {
                let len = seq.len();
                seq.try_into().map_err(|_| {
                    // TODO: unfortunately, we have to discard all of the values that
                    // we obtained simply because of the length mismatch :/
                    DeserializeErrors::err(DeserializeArrayError::LengthMismatch {
                        actual_length: len,
                        span: span.into(),
                        inner_errors: vec![],
                    })
                })
            }
            Err((Some(recovered_seq), err)) => {
                let len = recovered_seq.len();
                let array: Result<[T; N], _> = recovered_seq.try_into();

                Err(match array {
                    // The array has recoverable errors, and it's length is correct
                    Ok(recovered) => DeserializeErrors::recovered(
                        recovered,
                        DeserializeArrayError::CollectionError(err),
                    ),
                    // The array has recoverable errors, but it's length is incorrect
                    Err(_) => DeserializeErrors::err(
                        DeserializeArrayError::LengthMismatchAndCollectionError {
                            actual_length: len,
                            span: span.into(),
                            inner_errors: err,
                        },
                    ),
                })
            }
            Err((None, err)) => Err(DeserializeErrors::err(
                DeserializeArrayError::CollectionError(err),
            )),
        }
    }
}