///|
/// A merged configuration with the reports of its sources. Its root is always
/// a struct.
pub struct Config {
priv root : ConfigValue
priv reports : Array[SourceReport]
priv notes : Array[ConfigWarning]
priv provenance : Map[KeyPath, Array[Origin]]
priv secret_syntax : SecretSyntax
priv profiles : ProfileState
}
///|
/// Load the sources in list order over a complete empty file tree: a later
/// source wins. A file source raises `FileNotFound`, so this suits value
/// sources.
pub fn build(sources : Array[ConfigSource]) -> Config raise ConfigError {
match load(sources.fold(init=builder(), add), FileTree::new(complete=true)) {
Done(config) => config
Need(_) => abort("a complete file tree never asks for files")
}
}
///|
/// The origins of the leaf at `key`: one origin for a replaced leaf or list.
/// A struct has no origin of its own, so a struct or a missing key gives an
/// empty array.
pub fn Config::origins(self : Config, key : KeyPath) -> Array[Origin] {
self.provenance.get(key).map(origins => origins.copy()).unwrap_or([])
}
///|
/// The origins of every leaf, in tree order. Each array is not empty.
pub fn Config::provenance(self : Config) -> Array[(KeyPath, Array[Origin])] {
leaves(self.root, secret_syntax=self.secret_syntax).map(leaf =>
(leaf.0, self.origins(leaf.0))
)
}
///|
/// The source reports in merge order.
pub fn Config::reports(self : Config) -> Array[SourceReport] {
self.reports.map(report => {
..report,
candidates: report.candidates.copy(),
warnings: report.warnings.copy(),
})
}
///|
/// The warnings of the load: the warnings of the source providers, then the
/// warnings of each source report in merge order.
pub fn Config::warnings(self : Config) -> Array[ConfigWarning] {
[..self.notes, ..self.reports.map(report => report.warnings).flatten()]
}
///|
/// The merged value tree.
pub fn Config::value(self : Config) -> ConfigValue {
self.root
}
///|
/// The value at `key`, or `None` when no value is there.
pub fn Config::get(self : Config, key : KeyPath) -> ConfigValue? {
self.root.at(key.names)
}
///|
/// Resolve the secret at `key`. A plain string is a literal secret and does not
/// call the resolver. A secret reference calls `resolver` with the leaf origin.
pub fn[R : SecretResolver] Config::secret(
self : Config,
key : String,
resolver : R,
) -> Secret raise SecretError {
let path = KeyPath::parse(key) catch {
_ => raise MissingConfigKey(key={ names: [key] })
}
guard self.get(path) is Some(value) else { raise MissingConfigKey(key=path) }
match value.ion.kind() {
String(text) => return { text, }
_ => ()
}
guard self.secret_syntax is Standard else {
raise InvalidSecretValue(
reason="configuration key \{path} is not a secret reference",
)
}
guard secret_ref_of(value) is Some(reference) else {
raise InvalidSecretValue(reason="configuration key \{path} is not a secret")
}
let origin = match self.provenance.get(path) {
Some(origins) if !origins.is_empty() => origins[0]
_ =>
{
kind: InMemory,
source: "unknown",
priority: 0,
location: None,
}
}
resolver.resolve(reference, origin)
}
///|
/// The profile state recorded by the successful load.
pub fn Config::profiles(self : Config) -> ProfileState {
self.profiles
}