oh_my_toml 0.1.0

Awesome TOML configuration derive macro
Documentation
//! Impls for ints

use crate::DeserializeErrors;
use crate::{Key, Value};
use nonempty::NonEmpty;
use pastey::paste;
use std::num::{
    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
    NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
};

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

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

/// Generates `DeserializeValue` for all int types
macro_rules! impl_deserialize_for_ints {
    ($($num:ty, $kind:ident,)*) => {
        $(
            paste! {
                #[doc = concat!("Failed to deserialize [`", stringify!($num), "`](core::primitive::", stringify!($num),")")]
                #[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
                #[error("Type mismatch")]
                pub struct [<Deserialize $num:upper_camel Error>] {
                    /// Details on why deserialization failed
                    pub details: String,
                    /// Location of the error in the source file
                    #[label("{details}")]
                    pub span: SourceSpan,
                }

                impl $crate::Error for [<Deserialize $num:upper_camel Error>] {
                    fn expected_type() -> std::borrow::Cow<'static, str> {
                        match stringify!($kind) {
                            "i" => "int".into(),
                            "u" => "positive int".into(),
                            _ => unreachable!(),
                        }
                    }
                }

                #[doc = concat!("Failed to deserialize [`", stringify!([< NonZero $num:upper_camel >]),"`]")]
                #[derive(Diagnostic, Error, Debug, Clone, Eq, PartialEq, Hash)]
                #[error("Type mismatch")]
                pub struct [<Deserialize NonZero $num:upper_camel Error>] {
                    /// Describes the type that was expected and the type that was found.
                    pub details: String,
                    /// Span of the value in the source.
                    #[label("{details}")]
                    pub span: SourceSpan,
                }

                impl $crate::Error for [<Deserialize NonZero $num:upper_camel Error>] {
                    fn expected_type() -> std::borrow::Cow<'static, str> {
                        match stringify!($kind) {
                            "i" => "non-zero int".into(),
                            "u" => "non-zero positive int".into(),
                            _ => unreachable!(),
                        }
                    }
                }

                impl DeserializeValue<'_> for $num {
                    type Error = [<Deserialize $num:upper_camel Error>];

                    fn deserialize_value(_key: Key, value: Value) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                        if let Some(n) = value.as_ref().as_integer() {
                            let n = n.as_str().parse::<i64>().unwrap();
                            #[allow(irrefutable_let_patterns, reason = "macro")]
                            if let Ok(n_converted) = <$num>::try_from(n) {
                                Ok(n_converted)
                            } else {
                                let min = <$num>::MIN;
                                let max = <$num>::MAX;

                                let details = if stringify!($kind) == "u" && n.is_negative() {
                                    format!(
                                        "expected a positive int from `{min}` to `{max}`, but this is negative"
                                    )
                                } else {
                                    format!(
                                        "expected an int from `{min}` to `{max}`, but this int is out of bounds"
                                    )
                                };

                                Err(DeserializeErrors::err(
                                    [<Deserialize $num:upper_camel Error>] {
                                        details,
                                        span: value.span().into(),
                                    }
                                ))
                            }
                        } else {
                            // If the value is not an int, create a type mismatch error.
                            let err = [<Deserialize $num:upper_camel Error>] {
                                    details: [<Deserialize $num:upper_camel Error>]::details(
                                        value_type_description(&value)
                                    ),
                                    span: value.span().into(),
                                };
                            Err(DeserializeErrors::err(err))
                        }
                    }
                }

                impl DeserializeValue<'_> for [<NonZero $num:camel>] {
                    type Error = [<Deserialize NonZero $num:upper_camel Error>];

                    fn deserialize_value(_key: Key, value: Value) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                        if let Some(n) = value.as_ref().as_integer() {
                            let n = n.as_str().parse::<i64>().unwrap();
                            if n == 0 {
                                // If the int is zero, return a specific non-zero error.
                                return Err(DeserializeErrors::err(
                                     [<Deserialize NonZero $num:upper_camel Error>] {
                                        details: format!("expected a non-zero int, but found 0"),
                                        span: value.span().into(),
                                    }
                                ));
                            }

                            #[allow(irrefutable_let_patterns, reason = "macro")]
                            if let Ok(n_converted) = <$num>::try_from(n) {
                                Ok(
                                    // SAFETY: We just checked that `n == 0` and returned earlier
                                    // from the function, so n_converted is guaranteed non-zero.
                                    unsafe {
                                        <[<NonZero $num:camel>]>::new_unchecked(n_converted)
                                    }
                                )
                            } else {
                                let min = <$num>::MIN;
                                let max = <$num>::MAX;

                                // Determine the specific error message based on the int kind and value.
                                let details = if stringify!($kind) == "u" && n.is_negative() {
                                    format!(
                                        "expected a non-zero positive int from `{min}` to `{max}`, but this is negative"
                                    )
                                } else {
                                    format!(
                                        "expected a non-zero int from `{min}` to `{max}`, but this int is out of bounds"
                                    )
                                };

                                Err(DeserializeErrors::err(
                                    [<Deserialize NonZero $num:upper_camel Error>] {
                                        details,
                                        span: value.span().into(),
                                    }
                                ))
                            }
                        } else {
                            // If the value is not an int, create a type mismatch error.
                            let err = [<Deserialize NonZero $num:upper_camel Error>] {
                                details: [<Deserialize $num:upper_camel Error>]::details(
                                    value_type_description(&value)
                                ),
                                span: value.span().into(),
                            };

                            Err(DeserializeErrors::err(err))
                        }
                    }
                }
            }
        )*
    };
}

impl_deserialize_for_ints! {
    i8, i,
    i16, i,
    i32, i,
    i64, i,
    i128, i,
    isize, i,

    u8, u,
    u16, u,
    u32, u,
    u64, u,
    u128, u,
    usize, u,
}

/// Implement `DeserializeValue` for these atomic types
macro_rules! impl_deserialize_for_atomic {
    ($($n:literal $sn:literal)*) => {
        pastey::paste! {
            $(
                #[cfg(target_has_atomic = $sn)]
                impl DeserializeValue<'_> for std::sync::atomic::[<AtomicU $n:upper_camel>] {
                     type Error = [<DeserializeU $n:upper_camel Error>];

                     fn deserialize_value(
                         key: Key,
                         value: Value
                     ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                         <[< u $n >] as DeserializeValue>::deserialize_value(key, value)
                             .map(Into::into)
                             .map_err(|err| err.map(Into::into))
                     }
                }
                #[cfg(target_has_atomic = $sn)]
                impl DeserializeValue<'_> for std::sync::atomic::[<AtomicI $n:upper_camel>] {
                     type Error = [<DeserializeI $n:upper_camel Error>];

                     fn deserialize_value(
                         key: Key,
                         value: Value
                     ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                         <[< i $n >] as DeserializeValue>::deserialize_value(key, value)
                             .map(Into::into)
                             .map_err(|err| err.map(Into::into))
                     }
                }
            )*
        }
    };
}

// NOTE: `target_has_atomic` does not accept `stringify!()` so we have to do this
impl_deserialize_for_atomic!(8 "8" 16 "16" 32 "32" 64 "64");

#[cfg(target_has_atomic = "ptr")]
impl DeserializeValue<'_> for std::sync::atomic::AtomicUsize {
    type Error = DeserializeUsizeError;

    fn deserialize_value(
        key: Key,
        value: Value,
    ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
        <usize as DeserializeValue>::deserialize_value(key, value)
            .map(Into::into)
            .map_err(|err| err.map(Into::into))
    }
}

#[cfg(target_has_atomic = "ptr")]
impl DeserializeValue<'_> for std::sync::atomic::AtomicIsize {
    type Error = DeserializeIsizeError;

    fn deserialize_value(
        key: Key,
        value: Value,
    ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
        <isize as DeserializeValue>::deserialize_value(key, value)
            .map(Into::into)
            .map_err(|err| err.map(Into::into))
    }
}