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;
pub trait FromStrDeserializeable: core::fmt::Debug + Clone + FromStr<Err: Display> {
const HELP: Option<&str>;
fn expected_type() -> Cow<'static, str>;
fn deserialize_value(
_key: Key,
value: Value,
) -> Result<Self, DeserializeErrors<Self, NonEmpty<DeserializeFromStrError<Self>>>> {
value
.as_ref()
.as_str()
.ok_or_else(|| {
DeserializeErrors::err(DeserializeFromStrError::NotStr {
details: value_type_description(&value).to_string(),
span: value.span().into(),
help: Self::HELP,
_phantom: PhantomData,
})
})?
.parse()
.map_err(|error| {
DeserializeErrors::err(DeserializeFromStrError::Parse {
name: Self::expected_type(),
details: format!("{error}"),
span: value.span().into(),
help: Self::HELP,
_phantom: PhantomData,
})
})
}
}
#[derive(miette::Diagnostic, thiserror::Error, Debug, Clone, Eq, PartialEq, Hash)]
pub enum DeserializeFromStrError<D: FromStrDeserializeable> {
#[error("Type mismatch")]
NotStr {
details: String,
#[label("{details}")]
span: SourceSpan,
#[help]
help: Option<&'static str>,
_phantom: PhantomData<D>,
},
#[error("Invalid {name}")]
Parse {
name: Cow<'static, str>,
details: String,
#[label("{details}")]
span: SourceSpan,
#[help]
help: Option<&'static str>,
_phantom: PhantomData<D>,
},
}
impl<D: FromStrDeserializeable> Error for DeserializeFromStrError<D> {
fn expected_type() -> Cow<'static, str> {
D::expected_type()
}
}
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"
}