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::de::DeserializeOwned;
use serde_json::Value;

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

use super::LoadSession;
use crate::loader::LoadedConfig;
use crate::loader::canonical::{
    canonicalize_secret_paths, canonicalize_secret_paths_against_layers, canonicalize_value_paths,
};
use crate::loader::de::deserialize_with_path;
use crate::loader::load::runtime::{RuntimeMetadata, merge_layers_into_report, run_normalizers};
use crate::loader::load::secrets::normalize_secret_registration_paths;
use crate::loader::load::unknown_policy::{
    apply_unknown_field_policy, pre_deserialize_unknown_fields_error,
};
use crate::loader::load::validate::validate_loaded_config;
use crate::loader::migration_runtime::{
    apply_config_migrations, normalize_version_registration_path,
};
use crate::loader::unknown::{
    collect_known_paths, collect_known_paths_from_value, collect_suggestion_paths,
};

impl<T> LoadSession<T>
where
    T: Serialize + DeserializeOwned,
{
    pub(super) fn finish(mut self) -> Result<LoadedConfig<T>, ConfigError> {
        self.recanonicalize_metadata()?;
        let alias_overrides = self.metadata.alias_overrides()?;
        let pending_secret_paths = canonicalize_secret_paths(
            &normalize_secret_registration_paths(&self.secret_paths)?,
            &alias_overrides,
        );
        let secret_paths = canonicalize_secret_paths_against_layers(
            &pending_secret_paths,
            &self.layers,
            &alias_overrides,
        );
        let defaults_value =
            canonicalize_value_paths(&serde_json::to_value(&self.defaults)?, &self.metadata)?;
        let pre_deserialize_suggestion_paths =
            self.pre_deserialize_suggestion_paths(&defaults_value);
        let mut report = ConfigReport::new(
            defaults_value.clone(),
            secret_paths.clone(),
            alias_overrides,
        );
        let merged_layers = merge_layers_into_report(
            std::mem::take(&mut self.layers),
            defaults_value,
            &self.metadata,
            &secret_paths,
            &mut report,
        )?;
        let mut merged = merged_layers.merged;
        let string_coercion_paths = merged_layers.string_coercion_paths;

        self.apply_migrations(&mut merged, &mut report)?;
        let mut config = self.deserialize_config(
            &merged,
            &report,
            &string_coercion_paths,
            &pre_deserialize_suggestion_paths,
        )?;
        self.apply_post_deserialize_unknown_policy(
            &config,
            &merged,
            &mut report,
            &string_coercion_paths,
        )?;

        let mut runtime_metadata = RuntimeMetadata {
            alias_overrides: self.metadata.alias_overrides()?,
            secret_paths,
        };
        run_normalizers(
            self.normalizers,
            &mut config,
            &mut self.metadata,
            &pending_secret_paths,
            &mut runtime_metadata,
            &mut report,
        )?;

        report.replace_runtime_metadata(
            runtime_metadata.secret_paths.clone(),
            runtime_metadata.alias_overrides.clone(),
        );
        let final_value = validate_loaded_config(
            &config,
            &self.metadata,
            &runtime_metadata.secret_paths,
            &mut report,
            self.validators,
        )?;
        report.replace_final_value(final_value);

        Ok(LoadedConfig { config, report })
    }

    fn pre_deserialize_suggestion_paths(&self, defaults_value: &Value) -> BTreeMap<String, String> {
        let default_known_paths = collect_known_paths_from_value(defaults_value);
        collect_suggestion_paths(&self.metadata, &default_known_paths)
    }

    fn apply_migrations(
        &self,
        merged: &mut Value,
        report: &mut ConfigReport,
    ) -> Result<(), ConfigError> {
        if let Some((version_path, current_version)) = &self.config_version {
            let version_path = normalize_version_registration_path(version_path)?;
            apply_config_migrations(
                merged,
                &version_path,
                *current_version,
                &self.migrations,
                report,
            )?;
        }

        Ok(())
    }

    fn deserialize_config(
        &self,
        merged: &Value,
        report: &ConfigReport,
        string_coercion_paths: &BTreeSet<String>,
        pre_deserialize_suggestion_paths: &BTreeMap<String, String>,
    ) -> Result<T, ConfigError> {
        match deserialize_with_path(merged, report, string_coercion_paths) {
            Ok(config) => Ok(config),
            Err(error) => {
                if let Some(unknown_error) = pre_deserialize_unknown_fields_error::<T>(
                    self.unknown_field_policy,
                    merged,
                    &self.metadata,
                    pre_deserialize_suggestion_paths,
                    report,
                    string_coercion_paths,
                    &error,
                ) {
                    return Err(unknown_error);
                }
                Err(error)
            }
        }
    }

    fn apply_post_deserialize_unknown_policy(
        &self,
        config: &T,
        merged: &Value,
        report: &mut ConfigReport,
        string_coercion_paths: &BTreeSet<String>,
    ) -> Result<(), ConfigError> {
        let known_paths = collect_known_paths(config)?;
        let suggestion_paths = collect_suggestion_paths(&self.metadata, &known_paths);
        apply_unknown_field_policy::<T>(
            self.unknown_field_policy,
            merged,
            &suggestion_paths,
            report,
            string_coercion_paths,
        )
    }
}