1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use context::Context;
use std::env;
use std::error::Error as StdError;
use std::fmt;
use Error;

#[derive(Debug)]
struct EmptyError;

impl fmt::Display for EmptyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "parse method no implemented!")
    }
}

impl StdError for EmptyError {
    fn description(&self) -> &str {
        "Method parse is not implemented"
    }

    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        None
    }
}

/// Indicates that structure can be initialize from environment variables.
pub trait Yasec {
    /// Creates empty context and calls `with_context`.
    fn init() -> Result<Self, Error>
    where
        Self: Sized,
    {
        Self::with_prefix("")
    }

    fn with_prefix(prefix: impl AsRef<str>) -> Result<Self, Error>
    where
        Self: Sized,
    {
        Self::with_context(Context::new(prefix))
    }

    /// Initialize structure from environment variable from the passed context.
    /// By default calls `parse` method. It works for a basic type like number or string.
    /// The method is redefined for a sctructure with `#[derive(Yasec)`. In that case
    /// the method pick every field type and calls the method for the type.
    fn with_context(context: Context<Self>) -> Result<Self, Error>
    where
        Self: Sized,
    {
        let mut context = context;
        let env_var_name = context.infer_var_name();
        match env::var(&env_var_name) {
            Ok(ref x) => Self::parse(x).map_err(|e| Error::new(e, env_var_name, x.to_owned())),
            Err(e) => match context.take_default_var_value() {
                Some(default) => Ok(default),
                None => Err(Error::new(Box::new(e), env_var_name, None)),
            },
        }
    }

    /// Parses an environment variable value. It sould be implemented if an object is leaf of a
    /// configuration structure.
    fn parse(_val: &str) -> Result<Self, Box<dyn StdError>>
    where
        Self: Sized,
    {
        Err(Box::new(EmptyError))
    }
}

macro_rules! implement {
    ($x:ident) => {
        impl Yasec for $x {
            fn parse(val: &str) -> Result<Self, Box<dyn StdError>> {
                Ok(val.parse::<$x>()?)
            }
        }
    };
}

implement!(char);
implement!(u8);
implement!(u16);
implement!(u32);
implement!(u64);

implement!(i8);
implement!(i16);
implement!(i32);
implement!(i64);

implement!(f32);
implement!(f64);

impl Yasec for String {
    fn parse(val: &str) -> Result<Self, Box<dyn StdError>> {
        Ok(val.to_owned())
    }
}

impl<T: Yasec> Yasec for Option<T> {
    fn with_context(context: Context<Self>) -> Result<Self, Error> {
        let env_var_name = context.prefix();
        let env_var_result = env::var(&env_var_name);
        match env_var_result {
            Ok(ref x) => Self::parse(x).map_err(|e| Error::new(e, env_var_name, x.to_owned())),
            Err(_) => Ok(None),
        }
    }

    fn parse(val: &str) -> Result<Self, Box<dyn StdError>> {
        Ok(Some(T::parse(val)?))
    }
}