de_env 1.0.0

Deserialize environment variables into a struct
Documentation
use std::{
    borrow::Cow,
    ffi::{OsStr, OsString},
};

use serde::de::IntoDeserializer;

use crate::{Error, Result};

pub struct Value<'de>(Cow<'de, OsStr>);

impl<'de> From<Cow<'de, OsStr>> for Value<'de> {
    fn from(value: Cow<'de, OsStr>) -> Self {
        Self(value)
    }
}

impl<'de> From<&'de OsStr> for Value<'de> {
    fn from(value: &'de OsStr) -> Self {
        Self(Cow::Borrowed(value))
    }
}

impl<'de> From<OsString> for Value<'de> {
    fn from(value: OsString) -> Self {
        Self(Cow::Owned(value))
    }
}

impl<'de> From<Cow<'de, str>> for Value<'de> {
    fn from(value: Cow<'de, str>) -> Self {
        match value {
            Cow::Owned(string) => Self(Cow::Owned(OsString::from(string))),
            Cow::Borrowed(str) => Self(Cow::Borrowed(OsStr::new(str))),
        }
    }
}

impl<'de> From<&'de str> for Value<'de> {
    fn from(value: &'de str) -> Self {
        Self(Cow::Borrowed(OsStr::new(value)))
    }
}

impl<'de> From<String> for Value<'de> {
    fn from(value: String) -> Self {
        Self(Cow::Owned(OsString::from(value)))
    }
}

impl<'de> IntoDeserializer<'de, Error> for Value<'de> {
    type Deserializer = Self;

    fn into_deserializer(self) -> Self::Deserializer {
        self
    }
}

macro_rules! validate_unicode_and_parse {
    ($($ty:ident)*) => {
        paste::paste! {
            $(
                fn [<deserialize_ $ty>]<V>(self, visitor: V) -> Result<V::Value>
                where
                    V: serde::de::Visitor<'de>
                {
                    match self.0.to_str() {
                        Some(str) => visitor.[<visit_ $ty>](str.parse::<$ty>()?),
                        None => Err(Error::invalid_unicode(self.0.into_owned())),
                    }
                }
            )*
        }
    }
}

impl<'de> serde::de::Deserializer<'de> for Value<'de> {
    type Error = Error;

    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        match self.0.to_str() {
            Some(str) => visitor.visit_str(str),
            None => Err(Error::invalid_unicode(self.0.into_owned())),
        }
    }

    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        visitor.visit_string(
            self.0
                .into_owned()
                .into_string()
                .map_err(Error::invalid_unicode)?,
        )
    }

    fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        self.deserialize_str(visitor)
    }

    fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        let lowercase_input = self.0.to_str().map(str::to_lowercase);

        #[cfg(feature = "truthy-falsy")]
        match lowercase_input.as_deref() {
            Some("true" | "t" | "yes" | "y" | "on" | "1") => visitor.visit_bool(true),
            Some("false" | "f" | "no" | "n" | "off" | "0") => visitor.visit_bool(false),
            _ => Err(Error::invalid_bool(self.0.into_owned())),
        }

        #[cfg(not(feature = "truthy-falsy"))]
        match lowercase_input.as_deref() {
            Some("true") => visitor.visit_bool(true),
            Some("false") => visitor.visit_bool(false),
            _ => Err(Error::invalid_bool(self.0.into_owned())),
        }
    }

    fn deserialize_enum<V>(
        self,
        name: &'static str,
        variants: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        match self.0.to_str() {
            Some(str) => str
                .into_deserializer()
                .deserialize_enum(name, variants, visitor),
            None => Err(Error::invalid_unicode(self.0.into_owned())),
        }
    }

    fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        visitor.visit_newtype_struct(self)
    }

    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        visitor.visit_some(self)
    }

    // Required for skipping fields when visiting maps.
    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
    where
        V: serde::de::Visitor<'de>,
    {
        visitor.visit_unit()
    }

    validate_unicode_and_parse! {
        u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64
    }

    crate::de::util::unsupported_types! {
        bytes byte_buf unit unit_struct seq tuple
        tuple_struct map struct identifier any
    }
}

#[cfg(test)]
mod tests {
    use serde::Deserialize;

    use super::Value;

    #[test]
    fn deserialize_bool() {
        #[cfg(feature = "truthy-falsy")]
        let truthy = [
            "true", "TRUE", "t", "T", "yes", "YES", "y", "Y", "on", "ON", "1",
        ];

        #[cfg(not(feature = "truthy-falsy"))]
        let truthy = ["true", "TRUE"];

        for value in truthy {
            assert!(matches!(bool::deserialize(Value::from(value)), Ok(true)));
        }

        #[cfg(feature = "truthy-falsy")]
        let falsy = [
            "false", "FALSE", "f", "F", "no", "NO", "n", "N", "off", "OFF", "0",
        ];

        #[cfg(not(feature = "truthy-falsy"))]
        let falsy = ["false", "FALSE"];

        for value in falsy {
            assert!(matches!(bool::deserialize(Value::from(value)), Ok(false)));
        }

        assert!(bool::deserialize(Value::from("gibberish")).is_err());
    }

    #[test]
    fn deserialize_enum() {
        #[derive(serde::Deserialize, Debug)]
        #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
        enum Switch {
            On,
            Off,
            NewTypeVariant(bool),
            StructVariant { _field: bool },
        }

        assert!(matches!(
            Switch::deserialize(Value::from("ON")),
            Ok(Switch::On)
        ));

        assert!(matches!(
            Switch::deserialize(Value::from("OFF")),
            Ok(Switch::Off)
        ));

        assert!(Switch::deserialize(Value::from("NEW_TYPE_VARIANT")).is_err());
        assert!(Switch::deserialize(Value::from("STRUCT_VARIANT")).is_err());
        assert!(Switch::deserialize(Value::from("gibberish")).is_err());
    }

    #[test]
    fn deserialize_newtype_struct() {
        #[derive(serde::Deserialize, Debug, PartialEq)]
        struct NewType(u8);

        assert!(matches!(
            NewType::deserialize(Value::from("123")),
            Ok(NewType(123))
        ));
    }
}