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}