use std::{collections::HashMap, env, fmt, sync::Arc};
use crate::{
source::Hierarchical,
testing::MOCK_ENV_VARS,
value::{Map, Pointer, Value, ValueOrigin, WithOrigin},
ConfigSchema, ConfigSource,
};
pub trait FallbackSource: 'static + Send + Sync + fmt::Debug + fmt::Display {
fn provide_value(&self) -> Option<WithOrigin>;
}
#[derive(Debug, Clone, Copy)]
pub struct Env(pub &'static str);
impl fmt::Display for Env {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "env var {:?}", self.0)
}
}
impl Env {
pub fn get_raw(&self) -> Option<String> {
MOCK_ENV_VARS
.with(|cell| cell.borrow().get(self.0).cloned())
.or_else(|| env::var(self.0).ok())
}
}
impl FallbackSource for Env {
fn provide_value(&self) -> Option<WithOrigin> {
if let Some(value) = self.get_raw() {
let origin = ValueOrigin::Path {
source: Arc::new(ValueOrigin::EnvVars),
path: self.0.into(),
};
Some(WithOrigin::new(value.into(), Arc::new(origin)))
} else {
None
}
}
}
#[derive(Debug)]
pub struct Manual {
description: &'static str,
getter: fn() -> Option<WithOrigin>,
}
impl Manual {
pub const fn new(description: &'static str, getter: fn() -> Option<WithOrigin>) -> Self {
Self {
description,
getter,
}
}
}
impl fmt::Display for Manual {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.description)
}
}
impl FallbackSource for Manual {
fn provide_value(&self) -> Option<WithOrigin> {
(self.getter)()
}
}
#[derive(Debug)]
pub(crate) struct Fallbacks {
inner: HashMap<(String, &'static str), WithOrigin>,
origin: Arc<ValueOrigin>,
}
impl Fallbacks {
#[tracing::instrument(level = "debug", name = "Fallbacks::new", skip_all)]
pub(crate) fn new(schema: &ConfigSchema) -> Option<Self> {
let mut inner = HashMap::new();
for (prefix, config) in schema.iter_ll() {
for param in config.metadata.params {
let Some(fallback) = param.fallback else {
continue;
};
if let Some(mut val) = fallback.provide_value() {
tracing::trace!(
prefix = prefix.0,
config = ?config.metadata.ty,
param = param.rust_field_name,
provider = ?fallback,
"got fallback for param"
);
let origin = ValueOrigin::Synthetic {
source: val.origin.clone(),
transform: format!(
"fallback for `{}.{}`",
config.metadata.ty.name_in_code(),
param.rust_field_name,
),
};
val.origin = Arc::new(origin);
inner.insert((prefix.0.to_owned(), param.name), val);
}
}
}
if inner.is_empty() {
None
} else {
tracing::debug!(count = inner.len(), "got fallbacks for config params");
Some(Self {
inner,
origin: Arc::new(ValueOrigin::Fallbacks),
})
}
}
}
impl ConfigSource for Fallbacks {
type Kind = Hierarchical;
fn into_contents(self) -> WithOrigin<Map> {
let origin = self.origin;
let mut map = WithOrigin::new(Value::Object(Map::new()), origin.clone());
for ((prefix, name), value) in self.inner {
map.ensure_object(Pointer(&prefix), |_| origin.clone())
.insert(name.to_owned(), value);
}
map.map(|value| match value {
Value::Object(map) => map,
_ => unreachable!(),
})
}
}