use std::env; use crate::error::Result; use crate::map::Map; use crate::source::Source; use crate::value::{Value, ValueKind}; #[must_use] #[derive(Clone, Debug, Default)] pub struct Environment { /// Optional prefix that will limit access to the environment to only keys that /// begin with the defined prefix. /// /// A prefix with a separator of `_` is tested to be present on each key before its considered /// to be part of the source environment. /// /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`. prefix: Option, /// Optional character sequence that separates the prefix from the rest of the key prefix_separator: Option, /// Optional character sequence that separates each key segment in an environment key pattern. /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow /// an environment key of `REDIS_PASSWORD` to match. separator: Option, /// Optional character sequence that separates each env value into a vector. only works when try_parsing is set to true /// Once set, you cannot have type String on the same environment, unless you set list_parse_keys. list_separator: Option, /// A list of keys which should always be parsed as a list. If not set you can have only Vec or String (not both) in one environment. list_parse_keys: Option>, /// Ignore empty env values (treat as unset). ignore_empty: bool, /// Parses booleans, integers and floats if they're detected (can be safely parsed). try_parsing: bool, // Preserve the prefix while parsing keep_prefix: bool, /// Alternate source for the environment. This can be used when you want to test your own code /// using this source, without the need to change the actual system environment variables. /// /// ## Example /// /// ```rust /// # use config::{Environment, Config}; /// # use serde::Deserialize; /// # use std::collections::HashMap; /// # use std::convert::TryInto; /// # /// #[test] /// fn test_config() -> Result<(), config::ConfigError> { /// #[derive(Clone, Debug, Deserialize)] /// struct MyConfig { /// pub my_string: String, /// } /// /// let source = Environment::default() /// .source(Some({ /// let mut env = HashMap::new(); /// env.insert("MY_STRING".into(), "my-value".into()); /// env /// })); /// /// let config: MyConfig = Config::builder() /// .add_source(source) /// .build()? /// .try_into()?; /// assert_eq!(config.my_string, "my-value"); /// /// Ok(()) /// } /// ``` source: Option>, } impl Environment { #[deprecated(since = "0.12.0", note = "please use 'Environment::default' instead")] pub fn new() -> Self { Self::default() } pub fn with_prefix(s: &str) -> Self { Self { prefix: Some(s.into()), ..Self::default() } } pub fn prefix(mut self, s: &str) -> Self { self.prefix = Some(s.into()); self } pub fn prefix_separator(mut self, s: &str) -> Self { self.prefix_separator = Some(s.into()); self } pub fn separator(mut self, s: &str) -> Self { self.separator = Some(s.into()); self } /// When set and try_parsing is true, then all environment variables will be parsed as [`Vec`] instead of [`String`]. /// See [`with_list_parse_key`] when you want to use [`Vec`] in combination with [`String`]. pub fn list_separator(mut self, s: &str) -> Self { self.list_separator = Some(s.into()); self } /// Add a key which should be parsed as a list when collecting [`Value`]s from the environment. /// Once list_separator is set, the type for string is [`Vec`]. /// To switch the default type back to type Strings you need to provide the keys which should be [`Vec`] using this function. pub fn with_list_parse_key(mut self, key: &str) -> Self { if self.list_parse_keys == None { self.list_parse_keys = Some(vec![key.into()]) } else { self.list_parse_keys = self.list_parse_keys.map(|mut keys| { keys.push(key.into()); keys }); } self } pub fn ignore_empty(mut self, ignore: bool) -> Self { self.ignore_empty = ignore; self } /// Note: enabling `try_parsing` can reduce performance it will try and parse /// each environment variable 3 times (bool, i64, f64) pub fn try_parsing(mut self, try_parsing: bool) -> Self { self.try_parsing = try_parsing; self } pub fn keep_prefix(mut self, keep: bool) -> Self { self.keep_prefix = keep; self } pub fn source(mut self, source: Option>) -> Self { self.source = source; self } } impl Source for Environment { fn clone_into_box(&self) -> Box { Box::new((*self).clone()) } fn collect(&self) -> Result> { let mut m = Map::new(); let uri: String = "the environment".into(); let separator = self.separator.as_deref().unwrap_or(""); let prefix_separator = match (self.prefix_separator.as_deref(), self.separator.as_deref()) { (Some(pre), _) => pre, (None, Some(sep)) => sep, (None, None) => "_", }; // Define a prefix pattern to test and exclude from keys let prefix_pattern = self .prefix .as_ref() .map(|prefix| format!("{}{}", prefix, prefix_separator).to_lowercase()); let collector = |(key, value): (String, String)| { // Treat empty environment variables as unset if self.ignore_empty && value.is_empty() { return; } let mut key = key.to_lowercase(); // Check for prefix if let Some(ref prefix_pattern) = prefix_pattern { if key.starts_with(prefix_pattern) { if !self.keep_prefix { // Remove this prefix from the key key = key[prefix_pattern.len()..].to_string(); } } else { // Skip this key return; } } // If separator is given replace with `.` if !separator.is_empty() { key = key.replace(separator, "."); } let value = if self.try_parsing { // convert to lowercase because bool parsing expects all lowercase if let Ok(parsed) = value.to_lowercase().parse::() { ValueKind::Boolean(parsed) } else if let Ok(parsed) = value.parse::() { ValueKind::I64(parsed) } else if let Ok(parsed) = value.parse::() { ValueKind::Float(parsed) } else if let Some(separator) = &self.list_separator { if let Some(keys) = &self.list_parse_keys { if keys.contains(&key) { let v: Vec = value .split(separator) .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string()))) .collect(); ValueKind::Array(v) } else { ValueKind::String(value) } } else { let v: Vec = value .split(separator) .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string()))) .collect(); ValueKind::Array(v) } } else { ValueKind::String(value) } } else { ValueKind::String(value) }; m.insert(key, Value::new(Some(&uri), value)); }; match &self.source { Some(source) => source.clone().into_iter().for_each(collector), None => env::vars().for_each(collector), } Ok(m) } }