oh_my_toml 0.1.0

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

use std::borrow::Cow;
use std::fmt::Display;
use std::marker::PhantomData;
use std::str::FromStr;

use crate::DeserializeErrors;
use crate::DeserializeValue;
use crate::Error;
use crate::Key;
use crate::Value;
use nonempty::NonEmpty;

use miette::SourceSpan;

use crate::util::value_type_description;

/// Helper trait to implement `DeserializeValue` for values that can be computed from a string
pub trait FromStrDeserializeable: core::fmt::Debug + Clone + FromStr<Err: Display> {
    /// Help
    const HELP: Option<&str>;
    /// The type we were expecting
    fn expected_type() -> Cow<'static, str>;

    /// Deserialize value
    fn deserialize_value(
        _key: Key,
        value: Value,
    ) -> Result<Self, DeserializeErrors<Self, NonEmpty<DeserializeFromStrError<Self>>>> {
        value
            .as_ref()
            .as_str()
            // TOML value is not a string
            .ok_or_else(|| {
                DeserializeErrors::err(DeserializeFromStrError::NotStr {
                    details: value_type_description(&value).to_string(),
                    span: value.span().into(),
                    help: Self::HELP,
                    _phantom: PhantomData,
                })
            })?
            .parse()
            // TOML value is string, but it could not be parsed
            .map_err(|error| {
                DeserializeErrors::err(DeserializeFromStrError::Parse {
                    name: Self::expected_type(),
                    details: format!("{error}"),
                    span: value.span().into(),
                    help: Self::HELP,
                    _phantom: PhantomData,
                })
            })
    }
}

/// Error
#[derive(miette::Diagnostic, thiserror::Error, Debug, Clone, Eq, PartialEq, Hash)]
pub enum DeserializeFromStrError<D: FromStrDeserializeable> {
    /// Given argument is not a string
    #[error("Type mismatch")]
    NotStr {
        /// Details on why deserialization failed
        details: String,
        /// Location of the error in the source file
        #[label("{details}")]
        span: SourceSpan,
        /// Help
        #[help]
        help: Option<&'static str>,
        /// Phantom - We need to use a method on `D` in implementation of the `Error` trait
        _phantom: PhantomData<D>,
    },
    /// Failed to parse
    #[error("Invalid {name}")]
    Parse {
        /// Name of the type
        name: Cow<'static, str>,
        /// Details on why string parsing failed
        details: String,
        /// Location of the error in the source file
        #[label("{details}")]
        span: SourceSpan,
        /// Help
        #[help]
        help: Option<&'static str>,
        /// Phantom - We need to use a method on `D` in implementation of the `Error` trait
        _phantom: PhantomData<D>,
    },
}

impl<D: FromStrDeserializeable> Error for DeserializeFromStrError<D> {
    fn expected_type() -> Cow<'static, str> {
        D::expected_type()
    }
}

/// Implement for these traits
macro_rules! impl_for {
    (
        ty = $ty:ty,
        help = $help:expr,
        desc = $desc:literal
    ) => {
        impl FromStrDeserializeable for $ty {
            const HELP: Option<&str> = $help;

            fn expected_type() -> std::borrow::Cow<'static, str> {
                $desc.into()
            }
        }

        impl<'de> DeserializeValue<'de> for $ty {
            type Error = DeserializeFromStrError<Self>;

            fn deserialize_value(
                key: Key,
                value: Value,
            ) -> Result<Self, DeserializeErrors<Self, NonEmpty<Self::Error>>> {
                <Self as FromStrDeserializeable>::deserialize_value(key, value)
            }
        }
    };
}

use std::net;

impl_for! {
    ty = net::SocketAddr,
    help = Some(r#"Socket Address example: "127.0.0.1:8080""#),
    desc = "Socket Address"
}

impl_for! {
    ty = net::SocketAddrV4,
    help = Some(r#"Socket Address V4 example: "127.0.0.1:8080""#),
    desc = "Socket Address V4"
}

impl_for! {
    ty = net::SocketAddrV6,
    help = Some(r#"Socket Address V6 example: "127.0.0.1:8080""#),
    desc = "Socket Address V6"
}

impl_for! {
    ty = net::IpAddr,
    help = Some(r#"IP Address examples: "0xcb.0x0.0x71.0x00", "127.0.0.1""#),
    desc = "IP Address"
}

impl_for! {
    ty = net::Ipv4Addr,
    help = Some(r#"IPv4 Address examples: "0xcb.0x0.0x71.0x00", "127.0.0.1""#),
    desc = "IPv4 Address"
}

impl_for! {
    ty = net::Ipv6Addr,
    help = Some(r#"IPv6 Address examples: "0xcb.0x0.0x71.0x00", "127.0.0.1""#),
    desc = "IPv6 Address"
}

impl_for! {
    ty = char,
    help = Some(r#"character examples: "a", "z", "x""#),
    desc = "character"
}

impl_for! {
    ty = std::ffi::CString,
    help = None,
    desc = "string"
}

impl_for! {
    ty = std::path::PathBuf,
    help = None,
    desc = "path"
}

impl_for! {
    ty = std::ffi::OsString,
    help = None,
    desc = "string"
}

impl_for! {
    ty = String,
    help = None,
    desc = "string"
}