Skip to main content

envfury/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{env::VarError, str::FromStr};
4
5/// Error from reading the env var.
6#[derive(Debug, thiserror::Error)]
7#[error("error reading {key} env var: {reason}")]
8pub struct Error<T> {
9    /// The envirobment variable name.
10    pub key: &'static str,
11
12    /// The reason for the error.
13    #[source]
14    pub reason: T,
15}
16
17impl<T> Error<T> {
18    /// Create a new [`Error`].
19    pub fn new(key: &'static str, reason: T) -> Self {
20        Self { key, reason }
21    }
22
23    /// Map the reason to change the error type.
24    pub fn map_reason<U, F>(self, map: F) -> Error<U>
25    where
26        F: FnOnce(T) -> U,
27    {
28        let Self { key, reason } = self;
29        let reason = (map)(reason);
30        Error { key, reason }
31    }
32}
33
34/// Error while processing the value.
35#[derive(Debug, thiserror::Error)]
36pub enum ValueError<ParseError> {
37    /// The value was not a valid unicode.
38    #[error("value is not a valid unicode")]
39    NonUnicode,
40
41    /// The value could not be parsed from a string.
42    #[error("unable to parse: {0}")]
43    Parse(#[source] ParseError),
44}
45
46/// Error while processing a required variable.
47#[derive(Debug, thiserror::Error)]
48pub enum MustError<ParseError> {
49    /// The variable was not set.
50    #[error("not set")]
51    NotSet,
52
53    /// The value couldn't be processed.
54    #[error(transparent)]
55    Value(ValueError<ParseError>),
56}
57
58/// Error while processing a variable or parsing it's default.
59#[derive(Debug, thiserror::Error)]
60pub enum OrParseError<ParseError> {
61    /// The value couldn't be processed.
62    #[error(transparent)]
63    Value(ValueError<ParseError>),
64
65    /// The default could not be properly parsed.
66    #[error("unable to parse the default value while the variable was not set: {0}")]
67    ParseDefault(ParseError),
68}
69
70/// Error while processing a variable or executing a fallible else.
71#[derive(Debug, thiserror::Error)]
72pub enum OrElseTryError<ParseError, ElseError> {
73    /// The value couldn't be processed.
74    #[error(transparent)]
75    Value(ValueError<ParseError>),
76
77    /// The `else` handler failed.
78    #[error("the absent value handler failed: {0}")]
79    Else(#[source] ElseError),
80}
81
82impl<ParseError> From<OrParseError<ParseError>> for OrElseTryError<ParseError, ParseError> {
83    fn from(value: OrParseError<ParseError>) -> Self {
84        match value {
85            OrParseError::Value(value_error) => OrElseTryError::Value(value_error),
86            OrParseError::ParseDefault(parse_error) => OrElseTryError::Else(parse_error),
87        }
88    }
89}
90
91/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
92/// If the variable is not set - returns [`None`].
93/// Returns an error if the value is an invalid unicode or if the value could not be parsed.
94pub fn maybe<T>(key: &'static str) -> Result<Option<T>, Error<ValueError<T::Err>>>
95where
96    T: std::str::FromStr,
97    <T as std::str::FromStr>::Err: std::fmt::Display,
98{
99    let val = match std::env::var(key) {
100        Ok(val) => val,
101        Err(VarError::NotPresent) => return Ok(None),
102        Err(VarError::NotUnicode(_)) => return Err(Error::new(key, ValueError::NonUnicode)),
103    };
104    let val = val
105        .parse()
106        .map_err(|err| Error::new(key, ValueError::Parse(err)))?;
107    Ok(Some(val))
108}
109
110/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
111/// Returns an error if the value is not set, is an invalid unicode or if the value could not
112/// be parsed.
113pub fn must<T>(key: &'static str) -> Result<T, Error<MustError<T::Err>>>
114where
115    T: std::str::FromStr,
116    <T as std::str::FromStr>::Err: std::fmt::Display,
117{
118    match maybe(key) {
119        Ok(Some(val)) => Ok(val),
120        Ok(None) => Err(Error::new(key, MustError::NotSet)),
121        Err(err) => Err(err.map_reason(MustError::Value)),
122    }
123}
124
125/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
126/// If the variable is not set - returns a newly constructed value via the [`Default`] trait.
127/// Returns an error if the value is an invalid unicode or if the value could not be parsed.
128pub fn or_default<T>(key: &'static str) -> Result<T, Error<ValueError<T::Err>>>
129where
130    T: FromStr,
131    <T as FromStr>::Err: std::fmt::Display,
132    T: Default,
133{
134    let val = maybe(key)?;
135    Ok(val.unwrap_or_default())
136}
137
138/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
139/// If the variable is not set - returns the `default` argument.
140/// Returns an error if the value is an invalid unicode or if the value could not be parsed.
141pub fn or<T>(key: &'static str, default: T) -> Result<T, Error<ValueError<T::Err>>>
142where
143    T: FromStr,
144    <T as FromStr>::Err: std::fmt::Display,
145{
146    let val = maybe(key)?;
147    Ok(val.unwrap_or(default))
148}
149
150/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
151/// If the variable is not set - returns the result of calling `.into()` on the `default` argument.
152/// Returns an error if the value is an invalid unicode or if the value could not be parsed.
153pub fn or_into<T>(key: &'static str, default: impl Into<T>) -> Result<T, Error<ValueError<T::Err>>>
154where
155    T: FromStr,
156    <T as FromStr>::Err: std::fmt::Display,
157{
158    let val = maybe(key)?;
159    Ok(val.unwrap_or(default.into()))
160}
161
162/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
163/// If the variable is not set - returns the result of calling the `f` argument.
164/// Returns an error if the value is an invalid unicode or if the value could not be parsed.
165pub fn or_else<T, F>(key: &'static str, f: F) -> Result<T, Error<ValueError<T::Err>>>
166where
167    T: FromStr,
168    <T as FromStr>::Err: std::fmt::Display,
169    F: FnOnce() -> T,
170{
171    let val = maybe(key)?;
172    Ok(val.unwrap_or_else(f))
173}
174
175/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
176/// If the variable is not set - returns the `Ok`-result of calling the `f` argument.
177/// Returns an error if the value is an invalid unicode, if the value could not be parsed or if
178/// calling the `f` resulted in error.
179pub fn or_else_try<T, E, F>(key: &'static str, f: F) -> Result<T, Error<OrElseTryError<T::Err, E>>>
180where
181    T: FromStr,
182    <T as FromStr>::Err: std::fmt::Display,
183    F: FnOnce() -> Result<T, E>,
184{
185    let val = maybe(key).map_err(|err| err.map_reason(OrElseTryError::Value))?;
186    if let Some(val) = val {
187        return Ok(val);
188    }
189    let val = f().map_err(|err| Error::new(key, OrElseTryError::Else(err)))?;
190    Ok(val)
191}
192
193/// Get the value of environment variable `key` and parse it into the type `T` if variable is set.
194/// If the variable is not set - returns the result of calling the `default` and parsing the default
195/// argument.
196/// Returns an error if the variable value is an invalid unicode or if the value could not be
197/// parsed, or if the default could not be parsed.
198pub fn or_parse<T>(
199    key: &'static str,
200    default: impl Into<String>,
201) -> Result<T, Error<OrParseError<T::Err>>>
202where
203    T: FromStr,
204    <T as FromStr>::Err: std::fmt::Display,
205{
206    let val = maybe(key).map_err(|err| err.map_reason(OrParseError::Value))?;
207    if let Some(val) = val {
208        return Ok(val);
209    }
210    let val = default
211        .into()
212        .parse()
213        .map_err(|err| Error::new(key, OrParseError::ParseDefault(err)))?;
214    Ok(val)
215}