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