okcodes_config/
env.rs

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