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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
pub use ground_env_derive::FromEnv;
use std::borrow::Cow;

#[cfg(test)]
mod tests;

pub struct Context<'a> {
    pub prefix: std::cell::Cell<Option<&'a str>>,
    pub env: std::collections::HashMap<String, Result<String, std::ffi::OsString>>,
}

impl<'prefix> Context<'prefix> {
    pub fn env() -> Self {
        let env = std::env::vars_os()
            .filter_map(|(key, value)| {
                // Invalid key => missing key? (Hard to debug if you've messed up the key)
                let key = key.into_string().ok()?;
                // Invalid value => we can store the result.
                let value = value.into_string();
                Some((key, value))
            })
            .collect::<std::collections::HashMap<_, _>>();

        Self {
            prefix: std::cell::Cell::new(None),
            env,
        }
    }

    pub fn empty() -> Self {
        Self {
            prefix: std::cell::Cell::new(None),
            env: Default::default(),
        }
    }

    #[doc(hidden)]
    pub fn with_prefix<'a: 'prefix, T: FromEnv>(&self, prefix: Option<&'a str>) -> Result<T> {
        let old_prefix = self.prefix.replace(prefix);
        let out = T::from_ctx(self);
        self.prefix.replace(old_prefix);
        out
    }

    #[doc(hidden)]
    pub fn resolve(&self, key: &'static str) -> Result<Result<&str, String>> {
        let key_alloc;
        let key = if let Some(prefix) = self.prefix.get() {
            key_alloc = format!("{}{}", prefix, key);
            Cow::Owned(key_alloc)
        } else {
            Cow::Borrowed(key)
        };

        match self.env.get(key.as_ref()) {
            Some(t) => match t {
                Ok(va) => Ok(Ok(va.as_ref())),
                Err(o) => Err(Error::NotUnicode(key.into_owned(), o.clone())),
            },
            None => Ok(Err(key.into_owned())),
        }
    }
}

// impl Extend<(String, String)> for Context<'_> {
//     fn extend<T: IntoIterator<Item=(String, String)>>(&mut self, iter: T) {
//     }
//     fn extend_one(&mut self, item: (String, String)) {
//         todo!()
//     }
//     fn extend_reserve(&mut self, additional: usize) {
//         todo!()
//     }
// }
//
// impl Extend<(&String, &String)> for Context<'_> {
//     fn extend<T: IntoIterator<Item=(&String, &String)>>(&mut self, iter: T) {
//     }
//     fn extend_one(&mut self, item: (&String, &String)) {
//         todo!()
//     }
//     fn extend_reserve(&mut self, additional: usize) {
//         todo!()
//     }
// }
//
// impl Extend<(&str, &str)> for Context<'_> {
//     fn extend<T: IntoIterator<Item=(&str, &str)>>(&mut self, iter: T) {
//     }
//     fn extend_one(&mut self, item: (&str, &str)) {
//         todo!()
//     }
//     fn extend_reserve(&mut self, additional: usize) {
//         todo!()
//     }
// }

pub type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Unable to locate '{0}' within environment")]
    Missing(String),
    #[error("Unable to convert '{0}' into UTF-8")]
    NotUnicode(String, std::ffi::OsString),
    #[error("Parsing '{input}' as '{ty}' failed: {err}")]
    Parse {
        err: String,
        input: String,
        ty: &'static str,
    },
}

#[doc(hidden)]
pub fn transpose_err<T, E, U>(result: Result<Result<T, U>, E>) -> Result<Result<T, E>, U> {
    match result {
        Ok(result) => match result {
            Ok(value) => Ok(Ok(value)),
            Err(err) => Err(err),
        },
        Err(err) => Ok(Err(err)),
    }
}

pub trait FromEnv: Sized {
    fn from_env() -> Result<Self> {
        Self::from_ctx(&Context::env())
    }

    fn from_ctx(ctx: &Context<'_>) -> Result<Self>;
}

pub trait Parse: Sized {
    fn parse(input: &str) -> Result<Self>;
}

impl<T, E> Parse for T
where
    T: std::str::FromStr<Err = E>,
    E: std::error::Error,
{
    fn parse(value: &str) -> Result<Self> {
        std::str::FromStr::from_str(value).map_err(|err: E| Error::Parse {
            err: err.to_string(),
            input: value.to_string(),
            ty: std::any::type_name::<Self>(),
        })
    }
}