oh_my_toml 0.1.0

Awesome TOML configuration derive macro
Documentation
//! Impl for tuples

use crate::DeserializeErrors;
use nonempty::NonEmpty;

use pastey::paste;
use seq_macro::seq;

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

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

use crate::util::value_type_description;
use crate::{DeserializeValue, Error};

/// Generate impl for each tuple
macro_rules! impl_for_tuples {
    ( $( $arity:literal )* ) => {
        $(
            seq!(I in 0..$arity {
                paste! {
                    #[doc = concat!("Failed to deserialize tuple with ", stringify!($arity), " items")]
                    #[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
                    #[error("Failed to deserialize a tuple of size {}", stringify!($arity))]
                    #[allow(clippy::large_enum_variant, reason = "hard to fix it in a macro like this")]
                    pub enum [<DeserializeTuple $arity Error>]<
                        #( [<E I>], )*
                    >
                    where
                        #( [<E I>]: Error, )*
                    {
                        /// The TOML value is not an array
                        #[error("Type mismatch")]
                        NotArray {
                            /// Details on why deserialization failed
                            details: String,
                            /// Location of the error in the source file
                            #[label("{details}")]
                            span: SourceSpan,
                        },
                        #[doc = concat!("The TOML array does not have exactly ", $arity, " elements")]
                        #[error("Tuple length mismatch: expected {expected_length}, found {actual_length}")]
                        #[diagnostic(help(
                            "The TOML array must contain exactly {expected_length} elements for this tuple."
                        ))]
                        LengthMismatch {
                            /// The length that was expected
                            expected_length: usize,
                            /// The length that we got
                            actual_length: usize,
                            /// Location of the error in the source file
                            #[label("this tuple has {actual_length} elements")]
                            span: SourceSpan,
                            #(
                                #[doc = concat!("Errors from the deserialization of the tuple element ", I)]
                                #[related]
                                [< element I _errors >]: Vec< [< E I >] >,
                            )*
                        },
                        #(
                            #[doc = concat!("Error from deserializing element ", I, " of the tuple")]
                            #[error(transparent)]
                            #[diagnostic(transparent)]
                            [< Element I Error >]([< E I >]),
                        )*
                    }

                    // Implement TypeMismatchError for DeserializeTupleError.
                    // This implementation assumes E1 is the primary error type for the `new` and `expected_type` methods.
                    impl< #( [< E I >]: Error, )*> Error
                        for [< DeserializeTuple $arity Error>]< #( [< E I >], )* >
                    {
                        /// Returns a string describing the expected type for the tuple.
                        ///
                        /// For a tuple, the expected type is simply "a tuple".
                        ///
                        /// More specific details about inner types are handled by the element-specific errors
                        fn expected_type() -> Cow<'static, str> {
                            let list: Vec<std::borrow::Cow<'static, str>> = vec![
                                #( [< E I >]::expected_type(), )*
                            ];
                            let list = list.join(", ");

                            format!("list: [{list}]").into()
                        }
                    }

                    /// Implements `DeserializeValue` for tuples `(T, K)`.
                    ///
                    /// This allows deserializing a TOML array of exactly two elements into a Rust tuple.
                    impl<'v,  #( [< T I >], )* > DeserializeValue<'v> for ( #( [< T I >], )* )
                    where
                        #(
                            [< T I >]: DeserializeValue<'v>,
                        )*
                    {
                        type Error = [< DeserializeTuple $arity Error>]< #( [< T I >]::Error, )* >;

                        fn deserialize_value<'k>(
                            _key: Key<'k>,
                            value: Value<'v>
                        ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                            let span = value.span();
                            let description = value_type_description(&value);
                            let toml::de::DeValue::Array(xs) = value.into_inner() else {
                                // Not an array at all
                                return Err(DeserializeErrors::err(
                                     [< DeserializeTuple $arity Error>]::NotArray {
                                         span: span.into(),
                                         details: description.to_string()
                                     }
                                ));
                            };

                            if xs.len() != $arity {
                                // Length mismatch
                                return Err(DeserializeErrors::err(
                                    [< DeserializeTuple $arity Error>]::LengthMismatch {
                                        expected_length: $arity,
                                        actual_length: xs.len(),
                                        span: span.into(),
                                        // No inner errors from element deserialization yet, as the length was wrong
                                        #(
                                            [< element I _errors >]: vec![],
                                        )*
                                    }
                                ));
                            }

                            #[allow(unused_mut, reason = "macro")]
                            let mut tuple_errors = Vec::new();
                            #[allow(unused_mut, unused_variables, reason = "macro")]
                            let mut xs = xs.into_iter();

                            #(
                                let [< val_ e I >] = match [< T I >]::deserialize_value(
                                    Key::Index(I),
                                    xs.next().expect(concat!("len == ", stringify!($arity)))
                                ) {
                                    Ok(ok) => {
                                        Some(ok)
                                    }
                                    Err(DeserializeErrors { recovered, errors }) => {
                                        for e in errors {
                                            tuple_errors.push([< DeserializeTuple $arity Error>]::[< Element I Error >](e));
                                        }
                                        recovered
                                    }
                                };
                            )*

                            #[allow(irrefutable_let_patterns, reason = "macro")]
                            let ( #(Some( [< _e I >] ), )* ) = ( #( [< val_ e I >] ,)* ) else {
                                // One or both elements failed to deserialize.
                                // `tuple_errors` already contains the specific element errors.
                                return Err(DeserializeErrors {
                                    recovered: None,
                                    errors: NonEmpty::from_vec(tuple_errors).expect(
                                            concat!(
                                                "if at least 1 `<T I>::from_value` failed, it would have created ",
                                                "at least 1 error because `errs` is `NonEmpty` in all branches"
                                            )
                                        )
                                });
                            };

                            if let Some(tuple_errors) = NonEmpty::from_vec(tuple_errors) {
                                Err(DeserializeErrors {
                                    recovered: Some(( #( [< _e I >], )* )),
                                    errors: tuple_errors,
                                })
                            } else {
                                Ok(( #( [< _e I >], )* ))
                            }
                        }
                    }
                }
            });
        )*
    };
}

seq!(N in 0..=16 {
    impl_for_tuples!(#(N)*);
});