wrath 0.1.0

A structured approach to accessing environment variables
Documentation
//! Parsing environment variable values into structured data

use std::{
    convert::{From as FromTrait, Infallible},
    default::Default as DefaultTrait,
    ffi::OsString,
    str::FromStr as FromStrTrait,
};

use crate::error;

/// Parse via the [`FromStr`](FromStrTrait) trait
#[derive(Debug, Default)]
pub struct FromStr<T>(pub T);

/// "Parse" via the [`From`](FromTrait) trait
#[derive(Debug, Default)]
pub struct From<T>(pub T);

/// "Parse" via the [`OsString::into_string`][0] method
///
/// [0]: std::ffi::OsString::into_string
#[derive(Debug, Default)]
pub struct IntoString(pub String);

/// Parse or get a default value via the [`Default`](DefaultTrait) trait
#[derive(Debug, Default)]
pub struct Default<T>(pub T);

/// Parse an environment variable value into structured data
///
/// This trait is an abstraction over other parsing strategies, such as
/// [`FromStr`](FromStrTrait), and its interface is more specialized for dealing
/// with environment variables.
pub trait Parser
where
    Self: Sized,
{
    /// Type system indication of whether being unset is an error
    type ErrorUnset: error::Unset;

    /// The error that can occur while trying to parse the target type
    type ErrorParse;

    /// Parse an [`OsString`] into structured data
    ///
    /// # Errors
    ///
    /// In general, this function may fail if `input` is [`None`] or if parsing
    /// failed. However, some parsers, or combinations thereof, lack one or both
    /// of these failure modes, which is also valid.
    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>>;
}

impl<T> Parser for Option<T>
where
    T: Parser,
{
    type ErrorParse = T::ErrorParse;
    type ErrorUnset = Infallible;

    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>> {
        match T::parse(input) {
            Ok(x) => Ok(Some(x)),
            Err(e) => match e {
                error::Value::Unset(_) => Ok(None),
                error::Value::Parse(e) => Err(error::Value::Parse(e)),
            },
        }
    }
}

impl<T> Parser for FromStr<T>
where
    T: FromStrTrait,
{
    type ErrorParse = error::FromStr<T::Err>;
    type ErrorUnset = ();

    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>> {
        let input = input.ok_or(error::Value::Unset(()))?;

        let Some(str) = input.to_str() else {
            return Err(error::Value::Parse(error::FromStr::Convert(input)));
        };

        T::from_str(str)
            .map(Self)
            .map_err(|e| error::Value::Parse(error::FromStr::Parse(e)))
    }
}

impl Parser for IntoString {
    type ErrorParse = OsString;
    type ErrorUnset = ();

    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>> {
        input
            .ok_or(error::Value::Unset(()))?
            .into_string()
            .map(Self)
            .map_err(error::Value::Parse)
    }
}

impl<T> Parser for From<T>
where
    T: FromTrait<OsString>,
{
    type ErrorParse = Infallible;
    type ErrorUnset = ();

    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>> {
        let input = input.ok_or(error::Value::Unset(()))?;

        Ok(Self(T::from(input)))
    }
}

impl<T> Parser for Default<T>
where
    T: Parser + DefaultTrait,
{
    type ErrorParse = T::ErrorParse;
    type ErrorUnset = Infallible;

    fn parse(
        input: Option<OsString>,
    ) -> Result<Self, error::Value<Self::ErrorUnset, Self::ErrorParse>> {
        match T::parse(input) {
            Ok(x) => Ok(Self(x)),
            Err(e) => match e {
                error::Value::Unset(_) => Ok(Self(T::default())),
                error::Value::Parse(e) => Err(error::Value::Parse(e)),
            },
        }
    }
}