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}