1#![doc = include_str!("../README.md")]
2
3use std::{env::VarError, str::FromStr};
4
5#[derive(Debug, thiserror::Error)]
7#[error("error reading {key} env var: {reason}")]
8pub struct Error<T> {
9 pub key: &'static str,
11
12 #[source]
14 pub reason: T,
15}
16
17impl<T> Error<T> {
18 pub fn new(key: &'static str, reason: T) -> Self {
20 Self { key, reason }
21 }
22
23 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#[derive(Debug, thiserror::Error)]
36pub enum ValueError<ParseError> {
37 #[error("value is not a valid unicode")]
39 NonUnicode,
40
41 #[error("unable to parse: {0}")]
43 Parse(#[source] ParseError),
44}
45
46#[derive(Debug, thiserror::Error)]
48pub enum MustError<ParseError> {
49 #[error("not set")]
51 NotSet,
52
53 #[error(transparent)]
55 Value(ValueError<ParseError>),
56}
57
58#[derive(Debug, thiserror::Error)]
60pub enum OrParseError<ParseError> {
61 #[error(transparent)]
63 Value(ValueError<ParseError>),
64
65 #[error("unable to parse the default value while the variable was not set: {0}")]
67 ParseDefault(ParseError),
68}
69
70#[derive(Debug, thiserror::Error)]
72pub enum OrElseTryError<ParseError, ElseError> {
73 #[error(transparent)]
75 Value(ValueError<ParseError>),
76
77 #[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
91pub 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
110pub 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
125pub 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
138pub 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
150pub 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
162pub 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
175pub 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
193pub 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}