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}