figment/providers/
env.rs

1use std::fmt;
2
3use crate::{Profile, Provider, Metadata};
4use crate::coalesce::Coalescible;
5use crate::value::{Map, Dict};
6use crate::error::Error;
7use crate::util::nest;
8
9use uncased::{Uncased, UncasedStr};
10
11crate::util::cloneable_fn_trait!(
12    FilterMap: for<'a> Fn(&'a UncasedStr) -> Option<Uncased<'a>> + 'static
13);
14
15/// A [`Provider`] that sources its values from environment variables.
16///
17/// All key-lookups and comparisons are case insensitive, facilitated by the
18/// [`UncasedStr`] and [`Uncased`] types. By default, environment variable names
19/// are lowercased before being emitted as [key paths] in the provided data, but
20/// this default can be changed with [`Env::lowercase()`]. Environment variable
21/// values can contain structured data, parsed as a [`Value`], with syntax
22/// resembling TOML:
23///
24///   * [`Bool`]: `true`, `false` (e.g, `APP_VAR=true`)
25///   * [`Num::F64`]: any float containing `.`: (e.g, `APP_VAR=1.2`, `APP_VAR=-0.002`)
26///   * [`Num::USize`]: any unsigned integer (e.g, `APP_VAR=10`)
27///   * [`Num::Isize`]: any negative integer (e.g, `APP_VAR=-10`)
28///   * [`Array`]: delimited by `[]` (e.g, `APP_VAR=[true, 1.0, -1]`)
29///   * [`Dict`]: in the form `{key=value}` (e.g, `APP_VAR={key="value",num=10}`)
30///   * [`String`]: delimited by `"` (e.g, `APP_VAR=\"hi\"`)
31///   * [`String`]: anything else (e.g, `APP_VAR=hi`, `APP_VAR=[hi`)
32///
33/// Additionally, keys and strings delimited with `"` can contain the following
34/// escaped characters:
35///
36/// ```text
37/// \b         - backspace       (U+0008)
38/// \t         - tab             (U+0009)
39/// \n         - linefeed        (U+000A)
40/// \f         - form feed       (U+000C)
41/// \r         - carriage return (U+000D)
42/// \"         - quote           (U+0022)
43/// \\         - backslash       (U+005C)
44/// \uXXXX     - unicode         (U+XXXX)
45/// \UXXXXXXXX - unicode         (U+XXXXXXXX)
46/// ```
47///
48/// For example:
49///
50/// ```sh
51/// APP_VAR=\"hello\\nthere\"  => (what in Rust is) "hello\nthere"
52/// APP_VAR=\"hi\\u1234there\" => (what in Rust is) "hi\u{1234}there"
53/// APP_VAR=\"abc\\td\\n\"     => (what in Rust is) "abc\td\n"
54///
55/// APP_VAR={\"key\\nkey\"=123}`)
56/// APP_VAR={\"key.key\"=123}`)
57/// ```
58///
59/// Undelimited strings, or strings with invalid escape sequences, are
60/// interpreted exactly as written without any escaping.
61///
62/// [key paths]: crate::Figment#extraction
63/// [`Value`]: crate::value::Value
64/// [`Bool`]: crate::value::Value::Bool
65/// [`Num::F64`]: crate::value::Num::F64
66/// [`Num::USize`]: crate::value::Num::USize
67/// [`Num::ISize`]: crate::value::Num::ISize
68/// [`Array`]: crate::value::Value::Array
69/// [`String`]: crate::value::Value::String
70///
71/// # Key Paths (nesting)
72///
73/// Because environment variables names are emitted as [key paths] in the
74/// provided data, a nested dictionary is created for every component of the
75/// name delimited by `.`, each a parent of the next, with the leaf mapping to
76/// environment variable `Value`. For example, the environment variable
77/// `a.b.c=3` creates the mapping `a -> b -> c -> 3` in the emitted data.
78///
79/// Environment variable names cannot typically contain the `.` character, but
80/// another character can be used in its place by replacing that character in
81/// the name with `.` with [`Env::map()`]. The [`Env::split()`] method is a
82/// convenience method that does exactly this.
83///
84/// # Provider Details
85///
86///   * **Profile**
87///
88///     This provider does not set a profile.
89///
90///   * **Metadata**
91///
92///     This provider is named `environment variable(s)`. It does not specify a
93///     [`Source`](crate::Source). Interpolation makes path parts uppercase and
94///     delimited with a `.`.
95///
96///   * **Data**
97///
98///     The data emitted by this provider is single-level dictionary with the
99///     keys and values returned by [`Env::iter()`], which reads from the
100///     currently set environment variables and is customizable via the various
101///     inherent methods. The dictionary is emitted to the profile
102///     [`profile`](#structfield.profile), configurable via [`Env::profile()`].
103#[derive(Clone)]
104#[cfg_attr(nightly, doc(cfg(feature = "env")))]
105pub struct Env {
106    filter_map: Box<dyn FilterMap>,
107    /// The profile config data will be emitted to. Defaults to
108    /// [`Profile::Default`].
109    pub profile: Profile,
110    /// We use this to generate better metadata when available.
111    prefix: Option<String>,
112    /// We use this to generate better metadata when available.
113    lowercase: bool,
114}
115
116impl fmt::Debug for Env {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_set().entries(self.iter()).finish()
119    }
120}
121
122impl Env {
123    fn new() -> Self {
124        Env {
125            filter_map: Box::new(|key| Some(key.into())),
126            profile: Profile::Default,
127            prefix: None,
128            lowercase: true,
129        }
130    }
131
132    fn chain<F: Clone + 'static>(self, f: F) -> Self
133        where F: for<'a> Fn(Option<Uncased<'a>>) -> Option<Uncased<'a>>
134    {
135        let filter_map = self.filter_map;
136        Env {
137            filter_map: Box::new(move |key| f(filter_map(key))),
138            profile: self.profile,
139            prefix: self.prefix,
140            lowercase: true,
141        }
142    }
143
144    /// Constructs and `Env` provider that does not filter or map any
145    /// environment variables.
146    ///
147    /// ```rust
148    /// use serde::Deserialize;
149    /// use figment::{Figment, Jail, providers::Env};
150    ///
151    /// #[derive(Debug, PartialEq, Deserialize)]
152    /// struct Config {
153    ///     numbers: Vec<usize>,
154    ///     app_bar: String,
155    /// }
156    ///
157    /// Jail::expect_with(|jail| {
158    ///     jail.set_env("NUMBERS", "[1, 2, 3]");
159    ///     jail.set_env("APP_BAR", "hi");
160    ///
161    ///     let config: Config = Figment::from(Env::raw()).extract()?;
162    ///     assert_eq!(config, Config {
163    ///         numbers: vec![1, 2, 3],
164    ///         app_bar: "hi".into(),
165    ///     });
166    ///
167    ///     Ok(())
168    /// });
169    /// ```
170    #[inline(always)]
171    pub fn raw() -> Self {
172        Env::new()
173    }
174
175    /// Return an `Env` provider that filters environment variables to those
176    /// with the prefix `prefix` and maps to one without the prefix.
177    ///
178    /// ```rust
179    /// use serde::Deserialize;
180    /// use figment::{Figment, Jail, providers::Env};
181    ///
182    /// #[derive(Debug, PartialEq, Deserialize)]
183    /// struct Config {
184    ///     foo: usize,
185    ///     bar: String,
186    /// }
187    ///
188    /// Jail::expect_with(|jail| {
189    ///     jail.set_env("APP_FOO", 100);
190    ///     jail.set_env("APP_BAR", "hi");
191    ///
192    ///     let config: Config = Figment::from(Env::prefixed("APP_")).extract()?;
193    ///     assert_eq!(config, Config { foo: 100, bar: "hi".into() });
194    ///
195    ///     Ok(())
196    /// });
197    /// ```
198    pub fn prefixed(prefix: &str) -> Self {
199        let owned_prefix = prefix.to_string();
200        let mut env = Env::new()
201            .filter_map(move |key| match key.starts_with(&owned_prefix) {
202                true => Some(key[owned_prefix.len()..].into()),
203                false => None
204            });
205
206        env.prefix = Some(prefix.into());
207        env
208    }
209
210    /// Applys an additional filter to the keys of environment variables being
211    /// considered.
212    ///
213    /// ```rust
214    /// use figment::{Jail, providers::Env};
215    ///
216    /// Jail::expect_with(|jail| {
217    ///     jail.set_env("FOO_FOO", 100);
218    ///     jail.set_env("BAR_BAR", "hi");
219    ///     jail.set_env("foobar", "hi");
220    ///
221    ///     // We'll be left with `FOO_FOO=100` and `foobar=hi`.
222    ///     let env = Env::raw().filter(|k| k.starts_with("foo"));
223    ///     assert_eq!(env.iter().count(), 2);
224    ///
225    ///     // Filters chain, like iterator adapters. `FOO_FOO=100` remains.
226    ///     let env = env.filter(|k| k.as_str().contains('_'));
227    ///     assert_eq!(env.iter().count(), 1);
228    ///
229    ///     Ok(())
230    /// });
231    /// ```
232    pub fn filter<F: Clone + 'static>(self, filter: F) -> Self
233        where F: Fn(&UncasedStr) -> bool
234    {
235        self.chain(move |prev| prev.filter(|v| filter(&v)))
236    }
237
238    /// Applys an additional mapping to the keys of environment variables being
239    /// considered.
240    ///
241    /// ```rust
242    /// use figment::{Jail, providers::Env};
243    ///
244    /// Jail::expect_with(|jail| {
245    ///     jail.set_env("FOO_FOO", 100);
246    ///     jail.set_env("BAR_FOO", "hi");
247    ///     jail.set_env("foobar", "hi");
248    ///
249    ///     // This is like `prefixed("foo_")` without the filtering.
250    ///     let env = Env::raw().map(|k| match k.starts_with("foo_") {
251    ///         true => k["foo_".len()..].into(),
252    ///         false => k.into()
253    ///     });
254    ///
255    ///     // We now have `FOO=100`, `BAR_FOO=hi`, and `bar=hi`.
256    ///     assert_eq!(env.clone().filter(|k| k == "foo").iter().count(), 1);
257    ///
258    ///     // Mappings chain, like iterator adapters.
259    ///     let env = env.map(|k| match k.starts_with("bar_") {
260    ///         true => k["bar_".len()..].into(),
261    ///         false => k.into()
262    ///     });
263    ///
264    ///     // We now have `FOO=100`, `FOO=hi`, and `bar=hi`.
265    ///     assert_eq!(env.filter(|k| k == "foo").iter().count(), 2);
266    ///     Ok(())
267    /// });
268    /// ```
269    pub fn map<F: Clone + 'static>(self, mapper: F) -> Self
270        where F: Fn(&UncasedStr) -> Uncased<'_>
271    {
272        self.chain(move |prev| prev.map(|v| mapper(&v).into_owned()))
273    }
274
275    /// Simultanously filters and maps the keys of environment variables being
276    /// considered.
277    ///
278    /// The returned `Env` only yields values for which `f` returns `Some`.
279    ///
280    /// ```rust
281    /// use std::collections::HashMap;
282    /// use figment::{Jail, providers::Env};
283    /// use uncased::AsUncased;
284    ///
285    /// Jail::expect_with(|jail| {
286    ///     jail.clear_env();
287    ///     jail.set_env("FOO_FOO", 100);
288    ///     jail.set_env("BAR_BAR", "hi");
289    ///     jail.set_env("BAZ_BAZ", "200");
290    ///
291    ///     // We starts with all three variables in `Env::raw();
292    ///     let env = Env::raw();
293    ///     assert_eq!(env.iter().count(), 3);
294    ///
295    ///     // This is like `prefixed("foo_")` but with two prefixes.
296    ///     let env = env.filter_map(|k| {
297    ///         if k.starts_with("foo_") {
298    ///             Some(k["foo_".len()..].into())
299    ///         } else if k.starts_with("baz_") {
300    ///             Some(k["baz_".len()..].into())
301    ///         } else {
302    ///             None
303    ///         }
304    ///     });
305    ///
306    ///     // Now we have `FOO=100`, `BAZ="200"`.
307    ///     let values = env.iter().collect::<HashMap<_, _>>();
308    ///     assert_eq!(values.len(), 2);
309    ///     assert_eq!(values["foo".as_uncased()], "100");
310    ///     assert_eq!(values["baz".as_uncased()], "200");
311    ///     Ok(())
312    /// });
313    /// ```
314    pub fn filter_map<F: Clone + 'static>(self, f: F) -> Self
315        where F: Fn(&UncasedStr) -> Option<Uncased<'_>>
316    {
317        self.chain(move |prev| prev.and_then(|v| f(&v).map(|v| v.into_owned())))
318    }
319
320    /// Whether to lowercase keys before emitting them. Defaults to `true`.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use std::collections::HashMap;
326    ///
327    /// use figment::{Jail, Profile, Provider};
328    /// use figment::providers::Env;
329    ///
330    /// Jail::expect_with(|jail| {
331    ///     jail.clear_env();
332    ///     jail.set_env("FOO_BAR_BAZ", 1);
333    ///     jail.set_env("FOO_barBaz", 2);
334    ///
335    ///     // The default is to lower-case variable name keys.
336    ///     let env = Env::prefixed("FOO_");
337    ///     let data = env.data().unwrap();
338    ///     assert!(data[&Profile::Default].contains_key("bar_baz"));
339    ///     assert!(data[&Profile::Default].contains_key("barbaz"));
340    ///
341    ///     // This can be changed with `lowercase(false)`. You'll need to
342    ///     // arrange for deserialization to account for casing.
343    ///     let env = Env::prefixed("FOO_").lowercase(false);
344    ///     let data = env.data().unwrap();
345    ///     assert!(data[&Profile::Default].contains_key("BAR_BAZ"));
346    ///     assert!(data[&Profile::Default].contains_key("barBaz"));
347    ///
348    ///     Ok(())
349    /// });
350    /// ```
351    pub fn lowercase(mut self, lowercase: bool) -> Self {
352        self.lowercase = lowercase;
353        self
354    }
355
356    /// Splits each environment variable key at `pattern`, creating nested
357    /// dictionaries for each split. Specifically, nested dictionaries are
358    /// created for components delimited by `pattern` in the environment
359    /// variable string (3 in `A_B_C` if `pattern` is `_`), each dictionary
360    /// mapping to its parent.
361    ///
362    /// This is equivalent to: `self.map(|key| key.replace(pattern, "."))`.
363    ///
364    /// # Example
365    ///
366    /// ```rust
367    /// use serde::Deserialize;
368    /// use figment::{Figment, Jail, util::map, value::Dict, providers::Env};
369    ///
370    /// #[derive(Debug, PartialEq, Deserialize)]
371    /// struct Foo {
372    ///     key: usize,
373    /// }
374    ///
375    /// #[derive(Debug, PartialEq, Deserialize)]
376    /// struct Config {
377    ///     foo: Foo,
378    ///     map: Dict,
379    /// }
380    ///
381    /// Jail::expect_with(|jail| {
382    ///     // Without splitting: using structured data.
383    ///     jail.set_env("APP_FOO", "{key=10}");
384    ///     jail.set_env("APP_MAP", "{one=1,two=2.0}");
385    ///
386    ///     let config: Config = Figment::from(Env::prefixed("APP_")).extract()?;
387    ///     assert_eq!(config, Config {
388    ///         foo: Foo { key: 10 },
389    ///         map: map!["one".into() => 1u8.into(), "two".into() => 2.0.into()],
390    ///     });
391    ///
392    ///     // With splitting.
393    ///     jail.set_env("APP_FOO_KEY", 20);
394    ///     jail.set_env("APP_MAP_ONE", "1.0");
395    ///     jail.set_env("APP_MAP_TWO", "dos");
396    ///
397    ///     let config: Config = Figment::new()
398    ///         .merge(Env::prefixed("APP_").split("_"))
399    ///         .extract()?;
400    ///
401    ///     assert_eq!(config, Config {
402    ///         foo: Foo { key: 20 },
403    ///         map: map!["one".into() => 1.0.into(), "two".into() => "dos".into()],
404    ///     });
405    ///
406    ///     Ok(())
407    /// });
408    /// ```
409    pub fn split<P: Into<String>>(self, pattern: P) -> Self {
410        let pattern = pattern.into();
411        self.map(move |key| key.as_str().replace(&pattern, ".").into())
412    }
413
414    /// Filters out all environment variable keys contained in `keys`.
415    ///
416    /// ```rust
417    /// use figment::{Jail, providers::Env};
418    ///
419    /// Jail::expect_with(|jail| {
420    ///     jail.set_env("FOO_FOO", 1);
421    ///     jail.set_env("FOO_BAR", 2);
422    ///     jail.set_env("FOO_BAZ", 3);
423    ///     jail.set_env("FOO_BAM", 4);
424    ///
425    ///     let env = Env::prefixed("FOO_").ignore(&["bar", "baz"]);
426    ///     assert_eq!(env.clone().iter().count(), 2);
427    ///
428    ///     // Ignores chain.
429    ///     let env = env.ignore(&["bam"]);
430    ///     assert_eq!(env.iter().count(), 1);
431    ///     Ok(())
432    /// });
433    /// ```
434    pub fn ignore(self, keys: &[&str]) -> Self {
435        let keys: Vec<String> = keys.iter().map(|s| s.to_string()).collect();
436        self.filter(move |key| !keys.iter().any(|k| k.as_str() == key))
437    }
438
439    /// Filters out all environment variables keys _not_ contained in `keys`.
440    ///
441    /// ```rust
442    /// use figment::{Jail, providers::Env};
443    ///
444    /// Jail::expect_with(|jail| {
445    ///     jail.set_env("FOO_FOO", 1);
446    ///     jail.set_env("FOO_BAR", 2);
447    ///     jail.set_env("FOO_BAZ_BOB", 3);
448    ///     jail.set_env("FOO_BAM_BOP", 4);
449    ///
450    ///     let env = Env::prefixed("FOO_").only(&["bar", "baz_bob", "zoo"]);
451    ///     assert_eq!(env.iter().count(), 2);
452    ///
453    ///     jail.set_env("FOO_ZOO", 5);
454    ///     assert_eq!(env.iter().count(), 3);
455    ///
456    ///     let env = Env::prefixed("FOO_").split("_");
457    ///     assert_eq!(env.clone().only(&["bar", "baz.bob"]).iter().count(), 2);
458    ///     assert_eq!(env.clone().only(&["bar", "bam_bop"]).iter().count(), 1);
459    ///
460    ///     Ok(())
461    /// });
462    /// ```
463    pub fn only(self, keys: &[&str]) -> Self {
464        let keys: Vec<String> = keys.iter().map(|s| s.to_string()).collect();
465        self.filter(move |key| keys.iter().any(|k| k.as_str() == key))
466    }
467
468    /// Returns an iterator over all of the environment variable `(key, value)`
469    /// pairs that will be considered by `self`. The order is not specified.
470    ///
471    /// Keys are lower-cased with leading and trailing whitespace removed. Empty
472    /// keys, or partially empty keys, are not emitted.
473    ///
474    /// Any non-Unicode sequences in values are replaced with `U+FFFD
475    /// REPLACEMENT CHARACTER`. Values are otherwise unmodified.
476    ///
477    /// ```rust
478    /// use figment::{Jail, providers::Env};
479    ///
480    /// Jail::expect_with(|jail| {
481    ///     jail.set_env("FOO_B", 2);
482    ///     jail.set_env("FOO_A", 1);
483    ///     jail.set_env("FOO_C", 3);
484    ///
485    ///     let env = Env::prefixed("FOO_");
486    ///     let mut pairs: Vec<_> = env.iter().collect();
487    ///     pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
488    ///
489    ///     assert_eq!(pairs.len(), 3);
490    ///     assert_eq!(pairs[0], ("a".into(), "1".into()));
491    ///     assert_eq!(pairs[1], ("b".into(), "2".into()));
492    ///     assert_eq!(pairs[2], ("c".into(), "3".into()));
493    ///
494    ///     jail.set_env("FOO_D", 4);
495    ///     let mut pairs: Vec<_> = env.iter().collect();
496    ///     pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
497    ///
498    ///     assert_eq!(pairs.len(), 4);
499    ///     assert_eq!(pairs[3], ("d".into(), "4".into()));
500    ///
501    ///     Ok(())
502    /// });
503    /// ```
504    pub fn iter<'a>(&'a self) -> impl Iterator<Item=(Uncased<'static>, String)> + 'a {
505        std::env::vars_os()
506            .filter(|(k, _)| !k.is_empty())
507            .filter_map(move |(k, v)| {
508                let key = k.to_string_lossy();
509                let key = (self.filter_map)(UncasedStr::new(key.trim()))?;
510                let key = key.as_str().trim();
511                if key.split('.').any(|s| s.is_empty()) { return None }
512
513                let key = match self.lowercase {
514                    true => key.to_ascii_lowercase(),
515                    false => key.to_owned(),
516                };
517
518                Some((key.into(), v.to_string_lossy().to_string()))
519            })
520    }
521
522    /// Sets the profile config data will be emitted to.
523    ///
524    /// ```rust
525    /// use figment::{Profile, providers::Env};
526    ///
527    /// let env = Env::raw();
528    /// assert_eq!(env.profile, Profile::Default);
529    ///
530    /// let env = env.profile("debug");
531    /// assert_eq!(env.profile, Profile::from("debug"));
532    /// ```
533    pub fn profile<P: Into<Profile>>(mut self, profile: P) -> Self {
534        self.profile = profile.into();
535        self
536    }
537
538    /// Sets the profile config data will be emitted to to `global`.
539    ///
540    /// ```rust
541    /// use figment::{Profile, providers::Env};
542    ///
543    /// let env = Env::raw();
544    /// assert_eq!(env.profile, Profile::Default);
545    ///
546    /// let env = env.global();
547    /// assert_eq!(env.profile, Profile::Global);
548    /// ```
549    pub fn global(mut self) -> Self {
550        self.profile = Profile::Global;
551        self
552    }
553
554    /// A convenience method to retrieve the value for an environment variable
555    /// with name `name`. Retrieval is case-insensitive.
556    ///
557    /// ```rust
558    /// use figment::{Jail, providers::Env};
559    ///
560    /// Jail::expect_with(|jail| {
561    ///     jail.set_env("TESTING", 123);
562    ///     assert_eq!(Env::var("testing"), Some("123".to_string()));
563    ///     Ok(())
564    /// });
565    /// ```
566    pub fn var(name: &str) -> Option<String> {
567        for (env_key, val) in std::env::vars_os() {
568            let env_key = env_key.to_string_lossy();
569            if uncased::eq(env_key.trim(), name) {
570                return Some(val.to_string_lossy().trim().into());
571            }
572        }
573
574        None
575    }
576
577    /// A convenience method to retrieve the value for an environment variable
578    /// with name `name` or a default `default` if one is not set. Retrieval
579    /// is case-insensitive.
580    ///
581    /// ```rust
582    /// use figment::{Jail, providers::Env};
583    ///
584    /// Jail::expect_with(|jail| {
585    ///     jail.set_env("TESTING", 123);
586    ///     assert_eq!(Env::var_or("testing", "whoops"), "123");
587    ///     assert_eq!(Env::var_or("hi", "whoops"), "whoops");
588    ///     Ok(())
589    /// });
590    /// ```
591    pub fn var_or<S: Into<String>>(name: &str, default: S) -> String {
592        Self::var(name).unwrap_or_else(|| default.into())
593    }
594}
595
596impl Provider for Env {
597    fn metadata(&self) -> Metadata {
598        let mut md = Metadata::named("environment variable(s)")
599            .interpolater(move |_: &Profile, k: &[&str]| {
600                let keys: Vec<_> = k.iter()
601                    .map(|k| k.to_ascii_uppercase())
602                    .collect();
603
604                keys.join(".")
605            });
606
607        if let Some(prefix) = &self.prefix {
608            md.name = format!("`{}` {}", prefix.to_ascii_uppercase(), md.name).into();
609        }
610
611        md
612    }
613
614    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
615        let mut dict = Dict::new();
616        for (k, v) in self.iter() {
617            let nested_dict = nest(k.as_str(), v.parse().expect("infallible"))
618                .into_dict()
619                .expect("key is non-empty: must have dict");
620
621            dict = dict.merge(nested_dict);
622        }
623
624        Ok(self.profile.collect(dict))
625    }
626}