1use std::collections::HashMap;
7use std::fmt;
8
9pub trait EnvSource: Send + Sync {
14 fn var(&self, name: &str) -> Option<String>;
16}
17
18#[derive(Clone, Copy, Debug, Default)]
20pub struct ProcessEnv;
21
22impl EnvSource for ProcessEnv {
23 fn var(&self, name: &str) -> Option<String> {
24 std::env::var(name).ok()
25 }
26}
27
28#[derive(Clone, Default, PartialEq, Eq)]
30pub struct MapEnv(HashMap<String, String>);
31
32impl MapEnv {
33 #[must_use]
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 #[must_use]
41 pub fn with(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
42 self.0.insert(name.into(), value.into());
43 self
44 }
45}
46
47impl fmt::Debug for MapEnv {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 let mut names: Vec<&String> = self.0.keys().collect();
51 names.sort();
52 f.debug_tuple("MapEnv").field(&names).finish()
53 }
54}
55
56impl<K: Into<String>, V: Into<String>> FromIterator<(K, V)> for MapEnv {
57 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
58 Self(
59 iter.into_iter()
60 .map(|(k, v)| (k.into(), v.into()))
61 .collect(),
62 )
63 }
64}
65
66impl EnvSource for MapEnv {
67 fn var(&self, name: &str) -> Option<String> {
68 self.0.get(name).cloned()
69 }
70}
71
72pub(crate) fn lookup(
75 name: &str,
76 explicit: Option<&indexmap::IndexMap<String, String>>,
77 env: &dyn EnvSource,
78) -> Option<String> {
79 explicit
80 .and_then(|map| map.get(name))
81 .filter(|value| !value.is_empty())
82 .cloned()
83 .or_else(|| env.var(name).filter(|value| !value.is_empty()))
84}