blob: 432df2c8458ebdcbf586c3409859c351f7bfc5e7 [file] [log] [blame] [edit]
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<String>,
/// Optional character sequence that separates the prefix from the rest of the key
prefix_separator: Option<String>,
/// 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<String>,
/// 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<String>,
/// 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.
list_parse_keys: Option<Vec<String>>,
/// 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<Map<String, String>>,
}
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<String>`] instead of [`String`].
/// See [`with_list_parse_key`] when you want to use [`Vec<String>`] 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<String>`].
/// To switch the default type back to type Strings you need to provide the keys which should be [`Vec<String>`] 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<Map<String, String>>) -> Self {
self.source = source;
self
}
}
impl Source for Environment {
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
Box::new((*self).clone())
}
fn collect(&self) -> Result<Map<String, Value>> {
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::<bool>() {
ValueKind::Boolean(parsed)
} else if let Ok(parsed) = value.parse::<i64>() {
ValueKind::I64(parsed)
} else if let Ok(parsed) = value.parse::<f64>() {
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> = 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> = 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)
}
}