config/
env.rs

1use std::env;
2use std::ffi::OsString;
3
4#[cfg(feature = "convert-case")]
5use convert_case::{Case, Casing};
6
7use crate::error::Result;
8use crate::map::Map;
9use crate::source::Source;
10use crate::value::{Value, ValueKind};
11use crate::ConfigError;
12
13/// An environment source collects a dictionary of environment variables values into a hierarchical
14/// config Value type. We have to be aware how the config tree is created from the environment
15/// dictionary, therefore we are mindful about prefixes for the environment keys, level separators,
16/// encoding form (kebab, snake case) etc.
17#[must_use]
18#[derive(Clone, Debug, Default)]
19pub struct Environment {
20    /// Optional prefix that will limit access to the environment to only keys that
21    /// begin with the defined prefix.
22    ///
23    /// The prefix is tested to be present on each key before it's considered to be part of the
24    /// source environment. The separator character can be set through
25    /// [`prefix_separator`](Environment::prefix_separator()).
26    ///
27    /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`.
28    prefix: Option<String>,
29
30    /// Optional character sequence that separates the prefix from the rest of the key.
31    ///
32    /// Defaults to [`separator`](Environment::separator()) if that is set, otherwise `_`.
33    prefix_separator: Option<String>,
34
35    /// Optional character sequence that separates each key segment in an environment key pattern.
36    /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow
37    /// an environment key of `REDIS_PASSWORD` to match.
38    separator: Option<String>,
39
40    /// Optional directive to translate collected keys into a form that matches what serializers
41    /// that the configuration would expect. For example if you have the `kebab-case` attribute
42    /// for your serde config types, you may want to pass `Case::Kebab` here.
43    #[cfg(feature = "convert-case")]
44    convert_case: Option<Case>,
45
46    /// Optional character sequence that separates each env value into a vector. only works when `try_parsing` is set to true
47    /// Once set, you cannot have type String on the same environment, unless you set `list_parse_keys`.
48    list_separator: Option<String>,
49    /// A list of keys which should always be parsed as a list. If not set you can have only `Vec<String>` or `String` (not both) in one environment.
50    list_parse_keys: Option<Vec<String>>,
51
52    /// Ignore empty env values (treat as unset).
53    ignore_empty: bool,
54
55    /// Parses booleans, integers and floats if they're detected (can be safely parsed).
56    try_parsing: bool,
57
58    // Preserve the prefix while parsing
59    keep_prefix: bool,
60
61    /// Alternate source for the environment. This can be used when you want to test your own code
62    /// using this source, without the need to change the actual system environment variables.
63    ///
64    /// ## Example
65    ///
66    /// ```rust
67    /// # use config::{Environment, Config};
68    /// # use serde::Deserialize;
69    /// # use std::collections::HashMap;
70    /// # use std::convert::TryInto;
71    /// #
72    /// #[test]
73    /// fn test_config() -> Result<(), config::ConfigError> {
74    ///   #[derive(Clone, Debug, Deserialize)]
75    ///   struct MyConfig {
76    ///     pub my_string: String,
77    ///   }
78    ///
79    ///   let source = Environment::default()
80    ///     .source(Some({
81    ///       let mut env = HashMap::new();
82    ///       env.insert("MY_STRING".into(), "my-value".into());
83    ///       env
84    ///   }));
85    ///
86    ///   let config: MyConfig = Config::builder()
87    ///     .add_source(source)
88    ///     .build()?
89    ///     .try_into()?;
90    ///   assert_eq!(config.my_string, "my-value");
91    ///
92    ///   Ok(())
93    /// }
94    /// ```
95    source: Option<Map<String, String>>,
96}
97
98impl Environment {
99    /// Optional prefix that will limit access to the environment to only keys that
100    /// begin with the defined prefix.
101    ///
102    /// The prefix is tested to be present on each key before it's considered to be part of the
103    /// source environment. The separator character can be set through
104    /// [`prefix_separator`](Environment::prefix_separator()).
105    ///
106    /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`.
107    pub fn with_prefix(s: &str) -> Self {
108        Self {
109            prefix: Some(s.into()),
110            ..Self::default()
111        }
112    }
113
114    /// See [`Environment::with_prefix`]
115    pub fn prefix(mut self, s: &str) -> Self {
116        self.prefix = Some(s.into());
117        self
118    }
119
120    #[cfg(feature = "convert-case")]
121    pub fn with_convert_case(tt: Case) -> Self {
122        Self::default().convert_case(tt)
123    }
124
125    #[cfg(feature = "convert-case")]
126    pub fn convert_case(mut self, tt: Case) -> Self {
127        self.convert_case = Some(tt);
128        self
129    }
130
131    /// Optional character sequence that separates the prefix from the rest of the key.
132    ///
133    /// Defaults to [`separator`](Environment::separator()) if that is set, otherwise `_`.
134    pub fn prefix_separator(mut self, s: &str) -> Self {
135        self.prefix_separator = Some(s.into());
136        self
137    }
138
139    /// Optional character sequence that separates each key segment in an environment key pattern.
140    /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow
141    /// an environment key of `REDIS_PASSWORD` to match.
142    pub fn separator(mut self, s: &str) -> Self {
143        self.separator = Some(s.into());
144        self
145    }
146
147    /// When set and `try_parsing` is true, then all environment variables will be parsed as [`Vec<String>`] instead of [`String`].
148    /// See
149    /// [`with_list_parse_key`](Self::with_list_parse_key)
150    /// when you want to use [`Vec<String>`] in combination with [`String`].
151    pub fn list_separator(mut self, s: &str) -> Self {
152        self.list_separator = Some(s.into());
153        self
154    }
155
156    /// Add a key which should be parsed as a list when collecting [`Value`]s from the environment.
157    /// Once `list_separator` is set, the type for string is [`Vec<String>`].
158    /// To switch the default type back to type Strings you need to provide the keys which should be [`Vec<String>`] using this function.
159    pub fn with_list_parse_key(mut self, key: &str) -> Self {
160        let keys = self.list_parse_keys.get_or_insert_with(Vec::new);
161        keys.push(key.into());
162        self
163    }
164
165    /// Ignore empty env values (treat as unset).
166    pub fn ignore_empty(mut self, ignore: bool) -> Self {
167        self.ignore_empty = ignore;
168        self
169    }
170
171    /// Note: enabling `try_parsing` can reduce performance it will try and parse
172    /// each environment variable 3 times (bool, i64, f64)
173    pub fn try_parsing(mut self, try_parsing: bool) -> Self {
174        self.try_parsing = try_parsing;
175        self
176    }
177
178    // Preserve the prefix while parsing
179    pub fn keep_prefix(mut self, keep: bool) -> Self {
180        self.keep_prefix = keep;
181        self
182    }
183
184    /// Alternate source for the environment. This can be used when you want to test your own code
185    /// using this source, without the need to change the actual system environment variables.
186    ///
187    /// ## Example
188    ///
189    /// ```rust
190    /// # use config::{Environment, Config};
191    /// # use serde::Deserialize;
192    /// # use std::collections::HashMap;
193    /// # use std::convert::TryInto;
194    /// #
195    /// #[test]
196    /// fn test_config() -> Result<(), config::ConfigError> {
197    ///   #[derive(Clone, Debug, Deserialize)]
198    ///   struct MyConfig {
199    ///     pub my_string: String,
200    ///   }
201    ///
202    ///   let source = Environment::default()
203    ///     .source(Some({
204    ///       let mut env = HashMap::new();
205    ///       env.insert("MY_STRING".into(), "my-value".into());
206    ///       env
207    ///   }));
208    ///
209    ///   let config: MyConfig = Config::builder()
210    ///     .add_source(source)
211    ///     .build()?
212    ///     .try_into()?;
213    ///   assert_eq!(config.my_string, "my-value");
214    ///
215    ///   Ok(())
216    /// }
217    /// ```
218    pub fn source(mut self, source: Option<Map<String, String>>) -> Self {
219        self.source = source;
220        self
221    }
222}
223
224impl Source for Environment {
225    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
226        Box::new((*self).clone())
227    }
228
229    fn collect(&self) -> Result<Map<String, Value>> {
230        let mut m = Map::new();
231        let uri: String = "the environment".into();
232
233        let separator = self.separator.as_deref().unwrap_or("");
234        #[cfg(feature = "convert-case")]
235        let convert_case = &self.convert_case;
236        let prefix_separator = match (self.prefix_separator.as_deref(), self.separator.as_deref()) {
237            (Some(pre), _) => pre,
238            (None, Some(sep)) => sep,
239            (None, None) => "_",
240        };
241
242        // Define a prefix pattern to test and exclude from keys
243        let prefix_pattern = self
244            .prefix
245            .as_ref()
246            .map(|prefix| format!("{prefix}{prefix_separator}").to_lowercase());
247
248        let collector = |(key, value): (OsString, OsString)| {
249            let key = match key.into_string() {
250                Ok(key) => key,
251                // Key is not valid unicode, skip it
252                Err(_) => return Ok(()),
253            };
254
255            // Treat empty environment variables as unset
256            if self.ignore_empty && value.is_empty() {
257                return Ok(());
258            }
259
260            let mut key = key.to_lowercase();
261
262            // Check for prefix
263            if let Some(ref prefix_pattern) = prefix_pattern {
264                if key.starts_with(prefix_pattern) {
265                    if !self.keep_prefix {
266                        // Remove this prefix from the key
267                        key = key[prefix_pattern.len()..].to_string();
268                    }
269                } else {
270                    // Skip this key
271                    return Ok(());
272                }
273            }
274
275            // At this point, we don't know if the key is required or not.
276            // Therefore if the value is not a valid unicode string, we error out.
277            let value = value.into_string().map_err(|os_string| {
278                ConfigError::Message(format!(
279                    "env variable {key:?} contains non-Unicode data: {os_string:?}"
280                ))
281            })?;
282
283            // If separator is given replace with `.`
284            if !separator.is_empty() {
285                key = key.replace(separator, ".");
286            }
287
288            #[cfg(feature = "convert-case")]
289            if let Some(convert_case) = convert_case {
290                key = key.to_case(*convert_case);
291            }
292
293            let value = if self.try_parsing {
294                // convert to lowercase because bool parsing expects all lowercase
295                if let Ok(parsed) = value.to_lowercase().parse::<bool>() {
296                    ValueKind::Boolean(parsed)
297                } else if let Ok(parsed) = value.parse::<i64>() {
298                    ValueKind::I64(parsed)
299                } else if let Ok(parsed) = value.parse::<f64>() {
300                    ValueKind::Float(parsed)
301                } else if let Some(separator) = &self.list_separator {
302                    if let Some(keys) = &self.list_parse_keys {
303                        if keys.contains(&key) {
304                            let v: Vec<Value> = value
305                                .split(separator)
306                                .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_owned())))
307                                .collect();
308                            ValueKind::Array(v)
309                        } else {
310                            ValueKind::String(value)
311                        }
312                    } else {
313                        let v: Vec<Value> = value
314                            .split(separator)
315                            .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_owned())))
316                            .collect();
317                        ValueKind::Array(v)
318                    }
319                } else {
320                    ValueKind::String(value)
321                }
322            } else {
323                ValueKind::String(value)
324            };
325
326            m.insert(key, Value::new(Some(&uri), value));
327
328            Ok(())
329        };
330
331        match &self.source {
332            Some(source) => source
333                .clone()
334                .into_iter()
335                .map(|(key, value)| (key.into(), value.into()))
336                .try_for_each(collector),
337            None => env::vars_os().try_for_each(collector),
338        }?;
339
340        Ok(m)
341    }
342}