mod aliases_pass;
mod environment;
mod origin;
mod recover;
mod sections;
use figment::value::Dict;
use figment::Figment;
use std::path::Path;
use serde::de::DeserializeOwned;
use crate::error::{Error, Origin};
use crate::layer::{DEFAULTS_NAME, FLAGS_NAME, OVERRIDES_NAME};
use crate::snapshot::Snapshot;
use crate::source::LoadSpec;
use aliases_pass::apply_aliases;
use environment::{environment, merge_env_files};
use origin::convert;
pub(crate) use recover::recover;
pub(crate) const CACHED_NAME: &str = "the last configuration that worked";
const REMOTE_PREFIX: &str = "the remote store ";
pub(crate) fn load<T: DeserializeOwned>(spec: &LoadSpec<'_>) -> Result<T, Error> {
merged(spec)?.extract().map_err(convert)
}
pub(crate) fn merged(spec: &LoadSpec<'_>) -> Result<Figment, Error> {
Ok(apply_aliases(build(spec)?, spec).select(spec.key))
}
pub(crate) fn snapshot(spec: &LoadSpec<'_>) -> Result<Snapshot, Error> {
resolved(spec).map(|(snapshot, _figment)| snapshot)
}
pub(crate) fn resolved(spec: &LoadSpec<'_>) -> Result<(Snapshot, Figment), Error> {
let figment = merged(spec)?;
let mut snapshot = figment
.extract::<Dict>()
.map(Snapshot::new)
.map_err(convert)?;
let provenance = snapshot
.leaf_paths()
.into_iter()
.filter_map(|path| match origin_in(&figment, &path) {
Origin::Unknown => None,
origin => Some((path, origin)),
})
.collect();
snapshot.attach_provenance(provenance);
Ok((snapshot, figment))
}
pub(crate) fn origin_in(figment: &Figment, path: &str) -> Origin {
figment
.find_metadata(path)
.map_or(Origin::Unknown, origin::origin_of)
}
pub(crate) fn source_of(spec: &LoadSpec<'_>, path: &str) -> Result<Option<Origin>, Error> {
Ok(merged(spec)?.find_metadata(path).map(origin::origin_of))
}
pub(crate) fn is_set(spec: &LoadSpec<'_>, path: &str) -> Result<bool, Error> {
Ok(merged(spec)?.contains(path))
}
pub(crate) struct LayerDef {
name: &'static str,
active: fn(&LoadSpec<'_>) -> bool,
merge: fn(Figment, &LoadSpec<'_>) -> Result<Figment, Error>,
}
const LAYERS: &[LayerDef] = &[
LayerDef {
name: "default",
active: |spec| spec.defaults.is_some_and(|layer| !layer.is_empty()),
merge: merge_defaults,
},
LayerDef {
name: "discovered",
active: |spec| spec.search.is_some(),
merge: merge_discovered,
},
LayerDef {
name: "file",
active: |spec| !spec.sources.is_empty(),
merge: merge_listed,
},
LayerDef {
name: "remote",
active: |spec| {
spec.remote
.is_some_and(|remote| remote.document().is_some())
},
merge: merge_remote,
},
LayerDef {
name: ".env",
active: |spec| !spec.env_files.is_empty(),
merge: merge_env_files,
},
LayerDef {
name: "environment",
active: |spec| spec.full_env_prefix().is_some(),
merge: merge_environment,
},
LayerDef {
name: "binding",
active: |spec| {
spec.env_bindings
.is_some_and(|bindings| !bindings.is_empty())
},
merge: merge_bindings,
},
LayerDef {
name: "flag",
active: |spec| spec.flags.is_some_and(|layer| !layer.is_empty()),
merge: merge_flags,
},
LayerDef {
name: "override",
active: |spec| spec.overrides.is_some_and(|layer| !layer.is_empty()),
merge: merge_overrides,
},
];
fn build(spec: &LoadSpec<'_>) -> Result<Figment, Error> {
sections::validated_profile(spec)?;
let mut figment = Figment::new();
for layer in LAYERS {
if (layer.active)(spec) {
figment = (layer.merge)(figment, spec)?;
}
}
Ok(figment)
}
pub(crate) fn layer_figments(spec: &LoadSpec<'_>) -> Result<Vec<(&'static str, Figment)>, Error> {
sections::validated_profile(spec)?;
LAYERS
.iter()
.filter(|layer| (layer.active)(spec))
.map(|layer| {
Ok((
layer.name,
(layer.merge)(Figment::new(), spec)?.select(spec.key),
))
})
.collect()
}
fn merge_defaults(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
match spec.defaults {
Some(defaults) => Ok(figment.merge(defaults.provider(spec.key, DEFAULTS_NAME))),
None => Ok(figment),
}
}
fn merge_discovered(mut figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
let profile = sections::validated_profile(spec)?;
if let Some(search) = &spec.search {
for (path, format) in search.resolve() {
figment = sections::merge_file(figment, &path, format)?;
figment = sections::merge_profile_variant(figment, &path, format, profile.as_deref())?;
}
}
Ok(figment)
}
fn merge_listed(mut figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
let profile = sections::validated_profile(spec)?;
for source in spec.sources {
figment = sections::merge(figment, source)?;
if let (Some(path), Some(format)) = (source.path(), source.format()) {
figment = sections::merge_profile_variant(
figment,
Path::new(path),
format,
profile.as_deref(),
)?;
}
}
Ok(figment)
}
fn merge_remote(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
if let Some(remote) = spec.remote {
if let Some(document) = remote.document() {
let name = format!(
"{REMOTE_PREFIX}{}",
remote.describe().unwrap_or_else(|| "(unnamed)".to_owned())
);
return sections::merge_named_text(
figment,
&document.text,
document.format,
&name,
None,
);
}
}
Ok(figment)
}
fn merge_environment(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
match spec.full_env_prefix() {
Some(prefix) => {
if spec.strict_env {
environment::reject_ambiguous(&prefix)?;
}
Ok(figment.merge(environment(
&prefix,
spec.key,
spec.nest,
spec.allow_empty_env,
)))
}
None => Ok(figment),
}
}
fn merge_bindings(mut figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
if let Some(bindings) = spec.env_bindings {
for binding in bindings.providers(spec.key, spec.allow_empty_env) {
figment = figment.merge(binding);
}
}
Ok(figment)
}
fn merge_flags(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
match spec.flags {
Some(flags) => Ok(figment.merge(flags.provider(spec.key, FLAGS_NAME))),
None => Ok(figment),
}
}
fn merge_overrides(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
match spec.overrides {
Some(overrides) => Ok(figment.merge(overrides.provider(spec.key, OVERRIDES_NAME))),
None => Ok(figment),
}
}