use std::ffi::{OsStr, OsString};
use ortho_config::figment::value::{Dict, Map, Value};
use ortho_config::figment::{Error, Metadata, Profile, Provider};
use super::constants::ENV_PREFIX;
const NON_UTF8_KEY: &str = "non-UTF-8 environment key";
const NON_UTF8_VALUE: &str = "non-UTF-8 environment value";
pub(super) struct EnvironmentLayer {
entries: Vec<(OsString, OsString)>,
}
impl EnvironmentLayer {
pub(super) const fn new(entries: Vec<(OsString, OsString)>) -> Self {
Self { entries }
}
}
impl Provider for EnvironmentLayer {
fn metadata(&self) -> Metadata {
Metadata::named("injected environment variables")
}
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
let mut values = Dict::new();
for (key, value) in &self.entries {
let entry = parse_entry(key, value).map_err(|error| *error)?;
if let Some((components, parsed)) = entry {
insert_nested(&mut values, &components, parsed).map_err(Error::from)?;
}
}
let mut profiles = Map::new();
profiles.insert(Profile::Default, values);
Ok(profiles)
}
}
fn parse_entry(key: &OsStr, value: &OsStr) -> Result<Option<(Vec<String>, Value)>, Box<Error>> {
let key_text = match decode_key(key) {
KeyDecode::Decoded(text) => text,
KeyDecode::Unrelated => return Ok(None),
KeyDecode::Invalid => return Err(Box::new(reject(NON_UTF8_KEY, "non_utf8_key"))),
};
let Some(stripped) = strip_prefix_uncased(key_text.trim(), ENV_PREFIX) else {
return Ok(None);
};
let components: Vec<String> = stripped
.split("__")
.map(str::trim)
.filter(|component| !component.is_empty())
.map(str::to_ascii_lowercase)
.collect();
if components.is_empty() {
return Ok(None);
}
let Some(value_text) = value.to_str() else {
return Err(Box::new(reject(NON_UTF8_VALUE, "non_utf8_value")));
};
let parsed = match value_text.parse::<Value>() {
Ok(parsed) => parsed,
Err(never) => match never {},
};
Ok(Some((components, parsed)))
}
enum KeyDecode<'a> {
Decoded(&'a str),
Unrelated,
Invalid,
}
fn decode_key(key: &OsStr) -> KeyDecode<'_> {
match key.to_str() {
Some(text) => KeyDecode::Decoded(text),
None if claims_config_prefix(key) => KeyDecode::Invalid,
None => KeyDecode::Unrelated,
}
}
fn reject(message: &'static str, failure_kind: &'static str) -> Error {
tracing::warn!(failure_kind, "rejected non-UTF-8 injected configuration");
Error::from(message.to_owned())
}
fn claims_config_prefix(key: &OsStr) -> bool {
key.as_encoded_bytes()
.trim_ascii_start()
.get(..ENV_PREFIX.len())
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(ENV_PREFIX.as_bytes()))
}
fn strip_prefix_uncased<'a>(value: &'a str, prefix: &str) -> Option<&'a str> {
value
.get(..prefix.len())
.filter(|candidate| candidate.eq_ignore_ascii_case(prefix))
.and_then(|_| value.get(prefix.len()..))
}
fn insert_nested(target: &mut Dict, components: &[String], value: Value) -> Result<(), String> {
let Some((head, tail)) = components.split_first() else {
return Ok(());
};
if tail.is_empty() {
if let Some(existing) = target.get(head) {
let conflict = if matches!(existing, Value::Dict(..)) {
"a nested configuration key"
} else {
"an existing scalar configuration key"
};
return Err(format!(
"environment key `{}` conflicts with {conflict}",
components.join("__")
));
}
target.insert(head.clone(), value);
return Ok(());
}
let entry = target
.entry(head.clone())
.or_insert_with(|| Value::from(Dict::new()));
let Value::Dict(_, nested) = entry else {
return Err(format!(
"environment key `{}` conflicts with a scalar configuration key",
components.join("__")
));
};
insert_nested(nested, tail, value)
}
#[cfg(test)]
#[path = "environment_tests.rs"]
mod tests;