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}