mod aliases_pass;
mod environment;
#[cfg(any(feature = "ini", feature = "properties"))]
pub(crate) mod ini;
pub(crate) mod origin;
#[cfg(feature = "properties")]
pub(crate) mod properties;
mod recover;
mod secrets;
pub(crate) mod sections;
use std::collections::BTreeMap;
use std::path::Path;
use std::sync::Arc;
use serde::de::DeserializeOwned;
use crate::error::{Error, Origin};
use crate::snapshot::Snapshot;
use crate::source::LoadSpec;
use aliases_pass::apply_aliases;
pub(crate) use recover::recover;
pub(crate) fn load<T: DeserializeOwned>(spec: &LoadSpec<'_>) -> Result<T, Error> {
let snapshot = resolved(spec)?;
snapshot.extract().map_err(|error| {
let path = error.path();
match snapshot.source_of(&path) {
Some(origin) if !path.is_empty() => error.with_origin(origin.clone()),
_ => error,
}
})
}
pub(crate) fn snapshot(spec: &LoadSpec<'_>) -> Result<Snapshot, Error> {
resolved(spec)
}
pub(crate) fn resolved(spec: &LoadSpec<'_>) -> Result<Snapshot, Error> {
let engine = spec.engine();
let mut collected = contributions(spec)?;
let (mut tree, mut provenance) = crate::resolve::compose(engine, collected.take_layers())?;
apply_aliases(&mut tree, &mut provenance, &collected, spec)?;
for (path, origin) in &mut provenance {
if let crate::Origin::Env(_) = origin {
*origin = origin::refine_env(origin.clone(), path.split('.'), spec.nest);
}
}
let mut snapshot = Snapshot::new(tree);
snapshot.attach_provenance(provenance);
Ok(snapshot)
}
pub(crate) fn redacted(message: &str) -> String {
origin::without_backticked_values(message)
}
pub(crate) fn refine(origin: Origin, path: &str, nest: &str) -> Origin {
origin::refine_env(origin, path.split('.'), nest)
}
pub(crate) fn source_of(spec: &LoadSpec<'_>, path: &str) -> Result<Option<Origin>, Error> {
Ok(resolved(spec)?.source_of(path).cloned())
}
pub(crate) fn is_set(spec: &LoadSpec<'_>, path: &str) -> Result<bool, Error> {
Ok(resolved(spec)?.contains(path))
}
#[cfg(feature = "dotenv")]
pub(crate) fn env_file_entries(
spec: &LoadSpec<'_>,
) -> Result<Arc<BTreeMap<String, String>>, Error> {
let bound = spec
.env_bindings
.is_some_and(|bindings| !bindings.is_empty());
if spec.env_files.is_empty() || !bound {
return Ok(Arc::default());
}
let mut entries = BTreeMap::new();
for file in spec.env_files {
entries.extend(crate::dotenv::read(Path::new(file))?);
}
Ok(Arc::new(entries))
}
#[cfg(not(feature = "dotenv"))]
pub(crate) fn env_file_entries(
_spec: &LoadSpec<'_>,
) -> Result<Arc<BTreeMap<String, String>>, Error> {
Ok(Arc::default())
}
#[cfg(feature = "ini")]
pub(crate) fn __fuzz_ini(text: &str) -> Result<crate::Value, Error> {
ini::parse(text)
}
#[cfg(feature = "properties")]
pub(crate) fn __fuzz_properties(text: &str) -> Result<crate::Value, Error> {
properties::parse(text)
}
pub(crate) fn contributions(spec: &LoadSpec<'_>) -> Result<crate::resolve::Collected, Error> {
sections::validated_profile(spec)?;
let mut collected = crate::resolve::Collected::default();
collect_defaults(&mut collected, spec);
collect_discovered(&mut collected, spec)?;
collect_listed(&mut collected, spec)?;
collect_remote(&mut collected, spec)?;
secrets::collect(&mut collected, spec)?;
environment::collect_env_files(&mut collected, spec)?;
collect_environment(&mut collected, spec)?;
collect_bindings(&mut collected, spec)?;
collect_runtime(&mut collected, spec);
Ok(collected)
}
fn collect_defaults(into: &mut crate::resolve::Collected, spec: &LoadSpec<'_>) {
if let Some(defaults) = spec.defaults {
if !defaults.is_empty() {
into.layer("default", Origin::Runtime("default"), defaults.tree());
}
}
}
fn collect_discovered(
into: &mut crate::resolve::Collected,
spec: &LoadSpec<'_>,
) -> Result<(), Error> {
let profile = sections::validated_profile(spec)?;
let layout = sections::Layout::of(spec);
if let Some(search) = &spec.search {
for (path, format) in search.resolve() {
sections::collect_file(into, "discovered", &path, format, layout, spec.key)?;
sections::collect_profile_variant(
into,
"discovered",
&path,
format,
profile.as_deref(),
layout,
spec.key,
)?;
}
}
Ok(())
}
fn collect_listed(into: &mut crate::resolve::Collected, spec: &LoadSpec<'_>) -> Result<(), Error> {
let profile = sections::validated_profile(spec)?;
let layout = sections::Layout::of(spec);
for source in spec.sources {
sections::collect_source(into, "file", source, layout, spec.key)?;
if let (Some(path), Some(format)) = (source.path(), source.format()) {
sections::collect_profile_variant(
into,
"file",
Path::new(path),
format,
profile.as_deref(),
layout,
spec.key,
)?;
}
}
Ok(())
}
fn collect_remote(into: &mut crate::resolve::Collected, spec: &LoadSpec<'_>) -> Result<(), Error> {
let Some(remote) = spec.remote else {
return Ok(());
};
let Some(document) = remote.document() else {
return Ok(());
};
let store = remote.describe().unwrap_or_else(|| "(unnamed)".to_owned());
let parsed = crate::document::parse_with(spec.reader(), &document.text, document.format)
.map_err(|error| error.with_origin(Origin::Remote(store.clone())))?;
let (section, siblings) = sections::section_of(
sections::table_of(parsed),
sections::Layout::of(spec),
spec.key,
)?;
into.document("remote", &Origin::Remote(store), section, siblings);
Ok(())
}
fn collect_environment(
into: &mut crate::resolve::Collected,
spec: &LoadSpec<'_>,
) -> Result<(), Error> {
let Some(prefix) = spec.full_env_prefix() else {
return Ok(());
};
if spec.strict_env {
environment::reject_ambiguous(&prefix)?;
}
let tree = crate::env_layer::tree(&prefix, spec.nest, spec.allow_empty_env);
if let crate::Value::Table(values) = tree {
into.layer(
"environment",
Origin::Env(format!("{}*", prefix.to_ascii_uppercase())),
values,
);
}
Ok(())
}
fn collect_bindings(
into: &mut crate::resolve::Collected,
spec: &LoadSpec<'_>,
) -> Result<(), Error> {
let Some(bindings) = spec.env_bindings else {
return Ok(());
};
let fallback = env_file_entries(spec)?;
for (path, variable, value) in bindings.resolved(spec.allow_empty_env, fallback) {
let mut values = std::collections::BTreeMap::new();
crate::layer::insert_path(&mut values, &path, value);
into.layer("binding", Origin::Env(variable), values);
}
Ok(())
}
fn collect_runtime(into: &mut crate::resolve::Collected, spec: &LoadSpec<'_>) {
if let Some(flags) = spec.flags {
if !flags.is_empty() {
into.layer("flag", Origin::Runtime("command-line flag"), flags.tree());
}
}
if let Some(overrides) = spec.overrides {
if !overrides.is_empty() {
into.layer("override", Origin::Runtime("override"), overrides.tree());
}
}
}