use crate::{
entity::ConfigurationEntity,
loader::{self, Error, Loader},
};
use cfg_if::cfg_if;
use serde::Deserialize;
use std::fmt::{Display, Formatter};
use std::{env, fmt::Debug};
use url::Url;
pub const NAME: &str = "Environment-Variables";
pub const SCHEME_LIST: &[&str] = &["env"];
#[derive(Debug, Default, Clone)]
pub struct Env {
options: EnvOptions,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
struct EnvOptions {
prefix: String,
separator: String,
strip_prefix: bool,
}
impl Default for EnvOptions {
fn default() -> Self {
Self {
prefix: default::prefix(),
separator: default::separator(),
strip_prefix: default::strip_prefix(),
}
}
}
pub mod default {
#[inline]
pub fn prefix() -> String {
let mut prefix = option_env!("CARGO_BIN_NAME").unwrap_or("").to_string();
if prefix.is_empty() {
prefix = option_env!("CARGO_CRATE_NAME").unwrap_or("").to_string();
}
if !prefix.is_empty() {
prefix += separator().as_str();
}
prefix
}
#[inline(always)]
pub fn separator() -> String {
"__".to_string()
}
#[inline(always)]
pub fn strip_prefix() -> bool {
true
}
}
impl Env {
pub fn new() -> Self {
Default::default()
}
pub fn set_prefix<P: AsRef<str>>(&mut self, prefix: P) {
self.options.prefix = prefix.as_ref().to_string();
}
pub fn with_prefix<P: AsRef<str>>(mut self, prefix: P) -> Self {
self.set_prefix(prefix);
self
}
pub fn set_separator<S: AsRef<str>>(&mut self, separator: S) {
self.options.separator = separator.as_ref().to_string();
}
pub fn with_separator<S: AsRef<str>>(mut self, separator: S) -> Self {
self.set_separator(separator);
self
}
pub fn set_strip_prefix(&mut self, strip_prefix: bool) {
self.options.strip_prefix = strip_prefix;
}
pub fn with_strip_prefix(mut self, strip_prefix: bool) -> Self {
self.set_strip_prefix(strip_prefix);
self
}
}
impl Display for Env {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(NAME)
}
}
impl Loader for Env {
fn scheme_list(&self) -> Vec<String> {
SCHEME_LIST.iter().cloned().map(String::from).collect()
}
fn load(
&self,
url: &Url,
maybe_whitelist: Option<&[String]>,
_skip_soft_errors: bool,
) -> Result<Vec<(String, ConfigurationEntity)>, Error> {
let EnvOptions {
mut prefix,
mut separator,
mut strip_prefix,
} = loader::deserialize_query_string(NAME, url)?;
if self.options.prefix != default::prefix() {
prefix = self.options.prefix.clone()
}
if self.options.separator != default::separator() {
separator = self.options.separator.clone()
}
if self.options.strip_prefix != default::strip_prefix() {
strip_prefix = self.options.strip_prefix
}
if !separator.is_empty() && !prefix.is_empty() && !prefix.ends_with(separator.as_str()) {
prefix += separator.as_str()
}
let mut result = Vec::new();
env::vars()
.filter(|(key, _)| prefix.is_empty() || key.starts_with(prefix.as_str()))
.map(|(mut key, value)| {
if !prefix.is_empty() && strip_prefix {
key = key.chars().skip(prefix.chars().count()).collect::<String>()
}
(key, value)
})
.filter(|(key, _)| !key.is_empty())
.map(|(key, value)| {
let key_list = if separator.is_empty() {
[key].to_vec()
} else {
key.splitn(2, separator.as_str())
.map(|key| key.to_string())
.collect()
};
(key_list, value)
})
.filter(|(key_list, _)| !key_list[0].is_empty())
.map(|(mut key_list, value)| {
let plugin_name = key_list.remove(0).to_lowercase();
let key = if key_list.len() == 1 {
key_list.remove(0)
} else {
String::new()
};
(plugin_name, key, value)
})
.filter(|(_, key, _)| !key.is_empty())
.map(|(_plugin_name, _key, _value)| {
cfg_if! {
if #[cfg(feature = "tracing")] {
tracing::trace!(
plugin=_plugin_name,
key=_key,
value=_value,
"Detected environment-variable"
);
} else if #[cfg(feature = "logging")] {
log::trace!(
"msg=\"Detected environment-variable\" plugin={_plugin_name:?} key={_key:?} value={_value:?}"
);
}
}
(_plugin_name, _key, _value)
})
.filter(|(plugin_name, _, _)| {
maybe_whitelist
.as_ref()
.map(|whitelist| whitelist.contains(plugin_name))
.unwrap_or(true)
})
.for_each(|(plugin_name, key, value)| {
let key_value = format!("{key}={value:?}");
if let Some((_, _, configuration)) =
result.iter_mut().find(|(name, _, _)| *name == plugin_name)
{
*configuration += "\n";
*configuration += key_value.as_str();
} else {
result.push((plugin_name, format!("{prefix}*"), key_value));
}
});
Ok(result
.into_iter()
.map(|(plugin_name, key, contents)| {
(
plugin_name.clone(),
ConfigurationEntity::new(key, url.clone(), plugin_name, NAME)
.with_format("env")
.with_contents(contents),
)
})
.map(|(_plugin_name, _configuration)| {
cfg_if! {
if #[cfg(feature = "tracing")] {
tracing::trace!(
plugin=_plugin_name,
format=_configuration.maybe_format().unwrap_or(&"<unknown>".to_string()),
contents=_configuration.maybe_contents().unwrap(),
"Detected configuration from environment-variable"
);
} else if #[cfg(feature = "logging")] {
log::trace!(
"msg=\"Detected configuration from environment-variable\" plugin={_plugin_name:?} format={:?} contents={:?}",
_configuration.maybe_format().unwrap_or(&"<unknown>".to_string()),
_configuration.maybe_contents().unwrap(),
);
}
}
(_plugin_name, _configuration)
})
.collect())
}
}