ground_env/
lib.rs

1pub use ground_env_derive::FromEnv;
2use std::borrow::Cow;
3
4#[cfg(test)]
5mod tests;
6
7pub struct Context {
8    prefix: Vec<&'static str>,
9    env: std::collections::HashMap<String, Result<String, std::ffi::OsString>>,
10}
11
12impl Context {
13    pub fn env() -> Self {
14        let env = std::env::vars_os()
15            .filter_map(|(key, value)| {
16                // Invalid key => missing key? (Hard to debug if you've messed up the key)
17                let key = key.into_string().ok()?;
18                // Invalid value => we can store the result.
19                let value = value.into_string();
20                Some((key, value))
21            })
22            .collect::<std::collections::HashMap<_, _>>();
23
24        Self {
25            prefix: vec![],
26            env,
27        }
28    }
29
30    pub fn empty() -> Self {
31        Self {
32            prefix: vec![],
33            env: Default::default(),
34        }
35    }
36
37    #[doc(hidden)]
38    pub fn with_prefix<T: FromEnv>(&mut self, prefix: &'static str) -> Result<T> {
39        self.prefix.push(prefix);
40
41        let out = T::from_ctx(self);
42
43        let old = self.prefix.pop();
44
45        assert!(old.is_some(), "Any operation on the prefix should be self-contained. [Something being flattened removed an extra segment]");
46
47        out
48    }
49
50    #[doc(hidden)]
51    pub fn resolve(&self, key: &'static str) -> Result<Result<&str, String>> {
52        let mut key_alloc;
53
54        let key = if self.prefix.is_empty() {
55            Cow::Borrowed(key)
56        } else {
57            let len = self.prefix.iter()
58                .map(|item| item.len())
59                .sum::<usize>()
60                + key.len();
61            key_alloc = String::with_capacity(len);
62            for prefix in self.prefix.iter() {
63                key_alloc.push_str(*prefix);
64            }
65            key_alloc.push_str(key);
66            Cow::Owned(key_alloc)
67        };
68
69        match self.env.get(key.as_ref()) {
70            Some(t) => match t {
71                Ok(va) => Ok(Ok(va.as_ref())),
72                Err(o) => Err(Error::NotUnicode(key.into_owned(), o.clone())),
73            },
74            None => Ok(Err(key.into_owned())),
75        }
76    }
77}
78
79pub type Result<T, E = Error> = std::result::Result<T, E>;
80
81#[derive(Debug, thiserror::Error)]
82pub enum Error {
83    #[error("Unable to locate '{0}' within environment")]
84    Missing(String),
85    #[error("Unable to convert '{0}' into UTF-8")]
86    NotUnicode(String, std::ffi::OsString),
87    #[error("Parsing '{input}' as '{ty}' failed: {err}")]
88    Parse {
89        err: String,
90        input: String,
91        ty: &'static str,
92    },
93}
94
95pub trait FromEnv: Sized {
96    fn from_env() -> Result<Self> {
97        Self::from_ctx(&mut Context::env())
98    }
99
100    fn from_ctx(ctx: &mut Context) -> Result<Self>;
101}
102
103pub trait Parse: Sized {
104    fn parse(input: &str) -> Result<Self>;
105}
106
107impl<T, E> Parse for T
108    where
109        T: std::str::FromStr<Err = E>,
110        E: std::error::Error,
111{
112    fn parse(value: &str) -> Result<Self> {
113        std::str::FromStr::from_str(value).map_err(|err: E| Error::Parse {
114            err: err.to_string(),
115            input: value.to_string(),
116            ty: std::any::type_name::<Self>(),
117        })
118    }
119}
120
121#[doc(hidden)]
122pub fn transpose_err<T, E, U>(result: Result<Result<T, U>, E>) -> Result<Result<T, E>, U> {
123    match result {
124        Ok(result) => match result {
125            Ok(value) => Ok(Ok(value)),
126            Err(err) => Err(err),
127        },
128        Err(err) => Ok(Err(err)),
129    }
130}
131
132// Result<T, Result<T, E>> -> Result<T, E>
133#[doc(hidden)]
134pub fn flatten_err<T, E>(result: Result<T, Result<T, E>>) -> Result<T, E> {
135    match result {
136        Ok(value) => Ok(value),
137        Err(result) => match result {
138            Ok(value) => Ok(value),
139            Err(err) => Err(err),
140        },
141    }
142}