Skip to main content

config/
env.rs

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