1use std::{
25 char::ParseCharError,
26 collections::{BTreeMap, BTreeSet, HashSet},
27 convert::Infallible,
28 env::{VarError, remove_var},
29 ffi::OsStr,
30 fmt::{Debug, Display},
31 hash::{Hash, Hasher},
32 marker::PhantomData,
33 num::{
34 NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
35 NonZeroU64, NonZeroU128, NonZeroUsize, ParseFloatError, ParseIntError,
36 },
37 rc::Rc,
38 str::ParseBoolError,
39};
40
41pub trait FromEnvValue: Sized {
44 fn from_env_value(value: String) -> Self;
46}
47
48impl FromEnvValue for String {
49 fn from_env_value(value: String) -> Self {
50 value
51 }
52}
53
54pub trait TryFromEnvValue: Sized {
56 type Error;
58
59 fn try_from_env_value(value: String) -> Result<Self, Self::Error>;
65}
66
67impl<K: TryFromEnvValue + Eq + Hash, V: TryFromEnvValue> TryFromEnvValue for std::collections::HashMap<K, V> {
68 type Error = MapTryFromEnvError<K::Error, V::Error>;
69
70 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
71 let elements = value.split(',');
72 let size_hint = elements.size_hint().0;
73 let mut map = std::collections::HashMap::with_capacity(size_hint);
74
75 for line in elements {
76 if let Some((key, value)) = line.split_once('=') {
77 if value.contains('=') {
78 continue;
79 }
80
81 let key = K::try_from_env_value(key.to_owned()).map_err(MapTryFromEnvError::Key)?;
82 let value = V::try_from_env_value(value.to_owned()).map_err(MapTryFromEnvError::Value)?;
83
84 map.insert(key, value);
85 }
86 }
87
88 Ok(map)
89 }
90}
91
92impl<K: TryFromEnvValue + Ord, V: TryFromEnvValue> TryFromEnvValue for BTreeMap<K, V> {
93 type Error = MapTryFromEnvError<K::Error, V::Error>;
94
95 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
96 let elements = value.split(',');
97 let mut map = BTreeMap::new();
98
99 for line in elements {
100 if let Some((key, value)) = line.split_once('=') {
101 if value.contains('=') {
102 continue;
103 }
104
105 let key = K::try_from_env_value(key.to_owned()).map_err(MapTryFromEnvError::Key)?;
106 let value = V::try_from_env_value(value.to_owned()).map_err(MapTryFromEnvError::Value)?;
107
108 map.insert(key, value);
109 }
110 }
111
112 Ok(map)
113 }
114}
115
116#[derive(Debug)]
119pub enum MapTryFromEnvError<K, V> {
120 Key(K),
121 Value(V),
122}
123
124impl<K: Display, V: Display> Display for MapTryFromEnvError<K, V> {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 match self {
127 Self::Key(s) => Display::fmt(s, f),
128 Self::Value(v) => Display::fmt(v, f),
129 }
130 }
131}
132
133impl<K: std::error::Error + 'static, V: std::error::Error + 'static> std::error::Error for MapTryFromEnvError<K, V> {
134 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135 match self {
136 Self::Key(k) => Some(k),
137 Self::Value(v) => Some(v),
138 }
139 }
140}
141
142impl<T: TryFromEnvValue + Eq + Hash> TryFromEnvValue for HashSet<T> {
143 type Error = T::Error;
144
145 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
146 value
147 .split(',')
148 .map(|v| T::try_from_env_value(v.to_owned()))
149 .collect::<Result<_, T::Error>>()
150 }
151}
152
153impl<T: TryFromEnvValue + Ord> TryFromEnvValue for BTreeSet<T> {
154 type Error = T::Error;
155
156 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
157 value
158 .split(',')
159 .map(|v| T::try_from_env_value(v.to_owned()))
160 .collect::<Result<_, T::Error>>()
161 }
162}
163
164impl<T: TryFromEnvValue> TryFromEnvValue for Vec<T> {
165 type Error = T::Error;
166
167 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
168 value
169 .split(',')
170 .map(|v| T::try_from_env_value(v.to_owned()))
171 .collect::<Result<Vec<_>, T::Error>>()
172 }
173}
174
175#[cfg(feature = "tracing")]
176#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
177impl TryFromEnvValue for tracing::Level {
178 type Error = InvalidLevel;
179
180 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
181 match &*value.to_ascii_lowercase() {
182 "trace" => Ok(tracing::Level::TRACE),
183 "info" | "information" => Ok(tracing::Level::INFO),
184 "debug" => Ok(tracing::Level::DEBUG),
185 "warn" | "warning" => Ok(tracing::Level::WARN),
186 "error" => Ok(tracing::Level::ERROR),
187 level => Err(InvalidLevel(level.to_owned())),
188 }
189 }
190}
191
192#[cfg(feature = "tracing")]
193#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
194#[derive(Debug)]
195pub struct InvalidLevel(String);
198
199#[cfg(feature = "tracing")]
200#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
201impl std::fmt::Display for InvalidLevel {
202 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
203 write!(f, "invalid log level: '{}'", self.0)
204 }
205}
206
207#[cfg(feature = "tracing")]
208#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
209impl std::error::Error for InvalidLevel {}
210
211macro_rules! impl_try_from_env {
212 ($($(#[$meta:meta])* $Ty:ty: $Error:ty;)*) => {
213 $(
214 $(#[$meta])*
215 impl $crate::env::TryFromEnvValue for $Ty {
218 type Error = $Error;
219
220 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
221 value.parse()
222 }
223 }
224 )*
225 };
226}
227
228impl_try_from_env!(
229 bool: ParseBoolError;
230 char: ParseCharError;
231
232 f32: ParseFloatError;
233 f64: ParseFloatError;
234
235 NonZeroI8: ParseIntError;
236 NonZeroI16: ParseIntError;
237 NonZeroI32: ParseIntError;
238 NonZeroI64: ParseIntError;
239 NonZeroI128: ParseIntError;
240 NonZeroIsize: ParseIntError;
241
242 i8: ParseIntError;
243 i16: ParseIntError;
244 i32: ParseIntError;
245 i64: ParseIntError;
246 i128: ParseIntError;
247 isize: ParseIntError;
248
249 NonZeroU8: ParseIntError;
250 NonZeroU16: ParseIntError;
251 NonZeroU32: ParseIntError;
252 NonZeroU64: ParseIntError;
253 NonZeroU128: ParseIntError;
254 NonZeroUsize: ParseIntError;
255
256 u8: ParseIntError;
257 u16: ParseIntError;
258 u32: ParseIntError;
259 u64: ParseIntError;
260 u128: ParseIntError;
261 usize: ParseIntError;
262
263 std::path::PathBuf: Infallible;
264
265 #[cfg(feature = "sentry")]
266 #[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "sentry")))]
267 sentry_types::Dsn: sentry_types::ParseDsnError;
268
269 #[cfg(feature = "url")]
270 #[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "url")))]
271 url::Url: url::ParseError;
272);
273
274impl<T: FromEnvValue> TryFromEnvValue for T {
275 type Error = Infallible;
276
277 fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
278 Ok(T::from_env_value(value))
279 }
280}
281
282pub fn parse<K: Into<String>, V: FromEnvValue>(key: K) -> Result<V, VarError> {
284 std::env::var(key.into()).map(V::from_env_value)
285}
286
287pub fn try_parse<K: Into<String>, V: TryFromEnvValue>(key: K) -> Result<V, TryParseError<V::Error>> {
289 match std::env::var(key.into()) {
290 Ok(value) => V::try_from_env_value(value).map_err(TryParseError::Parse),
291 Err(e) => Err(TryParseError::System(e)),
292 }
293}
294
295pub fn try_parse_or<K: Into<String>, V: TryFromEnvValue>(
297 key: K,
298 default: impl FnOnce() -> V,
299) -> Result<V, TryParseError<V::Error>> {
300 match try_parse(key) {
301 Ok(value) => Ok(value),
302 Err(TryParseError::System(std::env::VarError::NotPresent)) => Ok(default()),
303 Err(e) => Err(e),
304 }
305}
306
307pub fn try_parse_or_else<K: Into<String>, V: TryFromEnvValue>(
309 key: K,
310 default: V,
311) -> Result<V, TryParseError<V::Error>> {
312 match std::env::var(key.into()) {
313 Ok(value) => V::try_from_env_value(value).map_err(TryParseError::Parse),
314 Err(VarError::NotPresent) => Ok(default),
315 Err(e) => Err(TryParseError::System(e)),
316 }
317}
318
319pub fn try_parse_optional<K: Into<String>, V: TryFromEnvValue>(key: K) -> Result<Option<V>, TryParseError<V::Error>> {
323 match std::env::var(key.into()) {
324 Ok(value) => V::try_from_env_value(value).map(Some).map_err(TryParseError::Parse),
325 Err(VarError::NotPresent) => Ok(None),
326 Err(e) => Err(TryParseError::System(e)),
327 }
328}
329
330#[derive(Debug)]
332pub enum TryParseError<V> {
333 System(VarError),
334 Parse(V),
335}
336
337impl<V: Display> Display for TryParseError<V> {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 match self {
340 TryParseError::System(s) => Display::fmt(s, f),
341 TryParseError::Parse(s) => Display::fmt(s, f),
342 }
343 }
344}
345
346impl<V: std::error::Error + 'static> std::error::Error for TryParseError<V> {
347 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
348 match self {
349 Self::System(v) => Some(v),
350 Self::Parse(v) => Some(v),
351 }
352 }
353}
354
355#[deprecated(
376 since = "0.1.0",
377 note = "trait is no longer needed as of azalia v0.1.0 (preparation of crates.io ver)"
378)]
379pub trait FromEnv: Sized {
380 fn from_env() -> Self;
382}
383
384pub trait TryFromEnv: Sized {
420 type Error;
422
423 fn try_from_env() -> Result<Self, Self::Error>;
425}
426
427#[allow(deprecated)]
428impl<T: FromEnv> TryFromEnv for T {
429 type Error = Infallible;
430
431 fn try_from_env() -> Result<Self, Self::Error> {
432 Ok(T::from_env())
433 }
434}
435
436pub struct EnvGuard {
455 name: String,
456 _non_send_and_sync: PhantomData<Rc<()>>,
457}
458
459impl EnvGuard {
460 pub fn enter(name: impl Into<String>) -> Self {
482 EnvGuard::enter_with(name, "1")
483 }
484
485 pub fn enter_with(name: impl Into<String>, value: impl AsRef<OsStr>) -> Self {
507 let name = name.into();
508
509 unsafe { std::env::set_var(&name, value) };
511 EnvGuard {
512 name,
513 _non_send_and_sync: PhantomData,
514 }
515 }
516}
517
518impl PartialEq for EnvGuard {
519 fn eq(&self, other: &Self) -> bool {
520 self.name == other.name
521 }
522}
523
524impl Eq for EnvGuard {}
525
526impl Hash for EnvGuard {
527 fn hash<H: Hasher>(&self, state: &mut H) {
528 self.name.hash(state);
529 }
530}
531
532impl Drop for EnvGuard {
533 fn drop(&mut self) {
534 unsafe { remove_var(&self.name) }
535 }
536}
537
538pub struct MultipleEnvGuard {
557 _variables: HashSet<EnvGuard>,
558 _non_send_sync: PhantomData<Rc<()>>,
559}
560
561impl MultipleEnvGuard {
562 pub fn enter(values: impl IntoIterator<Item = (impl Into<String>, impl AsRef<OsStr>)>) -> Self {
588 MultipleEnvGuard {
589 _non_send_sync: PhantomData,
590 _variables: values
591 .into_iter()
592 .map(|(key, value)| EnvGuard::enter_with(key, value))
593 .collect(),
594 }
595 }
596}
597
598pub fn enter(key: impl Into<String>, f: impl FnOnce()) {
604 let _guard = EnvGuard::enter(key);
605 f()
606}
607
608pub fn enter_with(key: impl Into<String>, value: impl AsRef<OsStr>, f: impl FnOnce()) {
614 let _guard = EnvGuard::enter_with(key, value);
615 f()
616}
617
618pub fn enter_multiple(iter: impl IntoIterator<Item = (impl Into<String>, impl AsRef<OsStr>)>, f: impl FnOnce()) {
625 let _guard = MultipleEnvGuard::enter(iter);
626 f()
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
633
634 #[test]
636 fn drop_multiple_env_guard() {
637 {
638 let mut guards = HashSet::new();
639 guards.insert(EnvGuard::enter("HELLO"));
640
641 assert!(std::env::var("HELLO").is_ok());
642 }
643
644 assert!(std::env::var("HELLO").is_err());
645
646 {
647 let _guard = MultipleEnvGuard::enter([("HELLO", "world")]);
648 assert!(std::env::var("HELLO").is_ok());
649 }
650
651 assert!(std::env::var("HELLO").is_err());
652 }
653
654 #[test]
655 fn map_try_from_env_value() {
656 assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("hello=world".into()).is_ok());
657 assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
658 assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
659 assert!(
660 <HashMap<String, String> as TryFromEnvValue>::try_from_env_value(
661 "hello=world,weow=fluff;wwww,s=true".into()
662 )
663 .is_ok()
664 );
665
666 assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("hello=world".into()).is_ok());
667 assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
668 assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
669 assert!(
670 <BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value(
671 "hello=world,weow=fluff;wwww,s=true".into()
672 )
673 .is_ok()
674 );
675 }
676
677 #[test]
678 fn set_try_from_env_value() {
679 assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("hello,world".into()).is_ok());
680 assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
681 assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
682 assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("hello,world,weow,fluff".into()).is_ok());
683
684 assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("hello,world".into()).is_ok());
685 assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
686 assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
687 assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("hello,world,weow,fluff".into()).is_ok());
688 }
689}