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}