tier 0.1.17

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

use serde_json::Value;

use crate::{ConfigError, ConfigMetadata, FieldMetadata};

use super::super::Layer;
use super::checks::{
    canonicalize_check_against_layers, canonicalize_check_against_value,
    canonicalize_check_with_aliases,
};
use super::runtime::{
    canonicalize_runtime_path, canonicalize_runtime_path_across_layers,
    canonicalize_runtime_paths_across_layers, canonicalize_runtime_paths_against_value,
};

pub(in crate::loader) fn canonicalize_alias_overrides_against_value(
    metadata: &ConfigMetadata,
    value: &Value,
) -> Result<BTreeMap<String, String>, ConfigError> {
    let aliases = metadata.alias_overrides()?;
    let mut canonicalized = BTreeMap::new();

    for (alias, canonical) in aliases {
        let alias = canonicalize_runtime_path(value, &alias);
        let canonical = canonicalize_runtime_path(value, &canonical);
        if alias == canonical {
            continue;
        }
        if let Some(first_path) = canonicalized.insert(alias.clone(), canonical.clone())
            && first_path != canonical
        {
            return Err(ConfigError::MetadataConflict {
                kind: "alias",
                name: alias,
                first_path,
                second_path: canonical,
            });
        }
    }

    Ok(canonicalized)
}

pub(in crate::loader) fn canonicalize_metadata_against_layers(
    metadata: &ConfigMetadata,
    layers: &[Layer],
) -> Result<ConfigMetadata, ConfigError> {
    let fields = metadata
        .fields()
        .iter()
        .cloned()
        .map(|mut field| {
            field.path = canonicalize_runtime_path_across_layers(&field.path, layers);
            field.aliases = canonicalize_runtime_paths_across_layers(field.aliases, layers);
            field
        })
        .collect::<Vec<_>>();
    validate_canonical_explicit_env_targets(&fields)?;

    let mut resolved = ConfigMetadata::new();
    resolved.extend_fields(fields);
    let aliases = resolved.alias_overrides()?;
    let checks = metadata.checks().iter().cloned().map(|check| {
        canonicalize_check_with_aliases(canonicalize_check_against_layers(check, layers), &aliases)
    });
    resolved.extend_checks(checks);
    Ok(resolved)
}

pub(in crate::loader) fn canonicalize_metadata_against_value(
    metadata: &ConfigMetadata,
    value: &Value,
) -> Result<ConfigMetadata, ConfigError> {
    let fields = metadata
        .fields()
        .iter()
        .cloned()
        .map(|mut field| {
            field.path = canonicalize_runtime_path(value, &field.path);
            field.aliases = canonicalize_runtime_paths_against_value(field.aliases, value);
            field
        })
        .collect::<Vec<_>>();
    validate_canonical_explicit_env_targets(&fields)?;

    let mut resolved = ConfigMetadata::new();
    resolved.extend_fields(fields);
    let aliases = resolved.alias_overrides()?;
    let checks = metadata.checks().iter().cloned().map(|check| {
        canonicalize_check_with_aliases(canonicalize_check_against_value(check, value), &aliases)
    });
    resolved.extend_checks(checks);
    Ok(resolved)
}

fn validate_canonical_explicit_env_targets(fields: &[FieldMetadata]) -> Result<(), ConfigError> {
    let mut targets = BTreeMap::<String, String>::new();
    for field in fields {
        let Some(env) = &field.env else {
            continue;
        };
        if let Some(first_env) = targets.insert(field.path.clone(), env.clone())
            && first_env != *env
        {
            return Err(ConfigError::MetadataConflict {
                kind: "environment override target",
                name: field.path.clone(),
                first_path: first_env,
                second_path: env.clone(),
            });
        }
    }
    Ok(())
}