tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use std::collections::{BTreeMap, BTreeSet};

use serde::Serialize;
use serde_json::Value;

use crate::error::ConfigError;
use crate::metadata::ConfigMetadata;
use crate::report::ConfigReport;

use crate::loader::canonical::{
    canonicalize_metadata_against_value, canonicalize_secret_paths_against_value,
};
use crate::loader::indexed_array::validate_indexed_array_paths;
use crate::loader::merge::{ensure_root_object, merge_values};
use crate::loader::path::ensure_path_safe_keys;
use crate::loader::policy::enforce_source_policies;
use crate::loader::trace::{record_deprecation_warnings, record_diff_steps, record_layer_steps};
use crate::loader::{Layer, NamedNormalizer, SourceKind, SourceTrace};

pub(super) struct MergedLayers {
    pub(super) merged: Value,
    pub(super) string_coercion_paths: BTreeSet<String>,
}

pub(super) struct RuntimeMetadata {
    pub(super) alias_overrides: BTreeMap<String, String>,
    pub(super) secret_paths: BTreeSet<String>,
}

pub(super) fn merge_layers_into_report(
    layers: Vec<Layer>,
    defaults_value: Value,
    metadata: &ConfigMetadata,
    secret_paths: &BTreeSet<String>,
    report: &mut ConfigReport,
) -> Result<MergedLayers, ConfigError> {
    let mut merged = defaults_value;
    ensure_root_object(&merged)?;

    let mut string_coercion_paths = BTreeSet::new();
    for layer in layers {
        string_coercion_paths.extend(layer.coercible_string_paths.iter().cloned());
        validate_indexed_array_paths(&merged, &layer)?;
        enforce_source_policies(&layer, metadata)?;
        report.record_source(layer.trace.clone());
        record_layer_steps(report, &layer, secret_paths);
        record_deprecation_warnings(report, &layer, metadata);
        if !matches!(layer.trace.kind, SourceKind::Default) {
            merge_values(
                &mut merged,
                layer.value,
                "",
                metadata,
                &layer.indexed_array_paths,
                &layer.direct_array_paths,
            );
        }
    }

    Ok(MergedLayers {
        merged,
        string_coercion_paths,
    })
}

pub(super) fn run_normalizers<T>(
    normalizers: Vec<NamedNormalizer<T>>,
    config: &mut T,
    metadata: &mut ConfigMetadata,
    pending_secret_paths: &BTreeSet<String>,
    runtime_metadata: &mut RuntimeMetadata,
    report: &mut ConfigReport,
) -> Result<(), ConfigError>
where
    T: Serialize,
{
    for normalizer in normalizers {
        let before = serde_json::to_value(&config)?;
        (normalizer.run)(config).map_err(|message| ConfigError::Normalize {
            name: normalizer.name.clone(),
            message,
        })?;
        let after = serde_json::to_value(&config)?;
        ensure_root_object(&after)?;
        ensure_path_safe_keys(&after, "")?;

        *metadata = canonicalize_metadata_against_value(metadata, &after)?;
        runtime_metadata.alias_overrides = metadata.alias_overrides()?;
        runtime_metadata.secret_paths = canonicalize_secret_paths_against_value(
            pending_secret_paths,
            &after,
            &runtime_metadata.alias_overrides,
        );

        let trace = SourceTrace::new(SourceKind::Normalization, normalizer.name.clone());
        report.record_source(trace.clone());
        record_diff_steps(
            report,
            &before,
            &after,
            &trace,
            &runtime_metadata.secret_paths,
        );
    }

    Ok(())
}