tier 0.1.17

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

use super::super::{
    ConfigMetadata, EffectiveSourcePolicy, EffectiveValidation, FieldMetadata, MetadataMatchScore,
};
use crate::metadata::paths::{metadata_match_score, try_normalize_metadata_path};

impl ConfigMetadata {
    /// Returns all merged field metadata entries.
    #[must_use]
    pub fn fields(&self) -> &[FieldMetadata] {
        &self.fields
    }

    /// Returns all normalized cross-field validation checks.
    #[must_use]
    pub fn checks(&self) -> &[super::super::ValidationCheck] {
        &self.checks
    }

    /// Returns the metadata entry for a normalized configuration path or alias.
    #[must_use]
    pub fn field(&self, path: &str) -> Option<&FieldMetadata> {
        let normalized = try_normalize_metadata_path(path).ok()?;
        let mut best = None::<(MetadataMatchScore, &FieldMetadata)>;
        for field in &self.fields {
            for candidate in
                std::iter::once(field.path.as_str()).chain(field.aliases.iter().map(String::as_str))
            {
                let Some(score) = metadata_match_score(&normalized, candidate) else {
                    continue;
                };

                match &mut best {
                    Some((best_score, best_field)) if score > *best_score => {
                        *best_score = score;
                        *best_field = field;
                    }
                    None => best = Some((score, field)),
                    _ => {}
                }
            }
        }

        best.map(|(_, field)| field)
    }

    pub(crate) fn matching_fields_for_path(&self, path: &str) -> Vec<&FieldMetadata> {
        let normalized = match try_normalize_metadata_path(path) {
            Ok(normalized) => normalized,
            Err(_) => return Vec::new(),
        };

        let mut matches = self
            .fields
            .iter()
            .filter_map(|field| {
                let best = std::iter::once(field.path.as_str())
                    .chain(field.aliases.iter().map(String::as_str))
                    .filter_map(|candidate| metadata_match_score(&normalized, candidate))
                    .max();
                best.map(|score| (score, field))
            })
            .collect::<Vec<_>>();
        matches.sort_by(|left, right| {
            left.0
                .cmp(&right.0)
                .then_with(|| left.1.path.cmp(&right.1.path))
        });
        matches.into_iter().map(|(_, field)| field).collect()
    }

    pub(crate) fn effective_source_policy_for(&self, path: &str) -> Option<EffectiveSourcePolicy> {
        let mut policy = EffectiveSourcePolicy::default();
        let mut has_policy = false;

        for field in self.matching_fields_for_path(path) {
            if field.allowed_sources.is_some() || field.denied_sources.is_some() {
                has_policy = true;
                policy.apply_field(field);
            }
        }

        has_policy.then_some(policy)
    }

    pub(crate) fn effective_validations_for(&self, path: &str) -> Vec<EffectiveValidation> {
        let Some(field) = self.effective_field_for(path) else {
            return Vec::new();
        };

        field
            .validations
            .iter()
            .cloned()
            .map(|rule| EffectiveValidation {
                field: field.clone(),
                rule,
            })
            .collect()
    }

    pub(crate) fn effective_field_for(&self, path: &str) -> Option<FieldMetadata> {
        let mut matches = self.matching_fields_for_path(path).into_iter();
        let mut effective = matches.next()?.clone();
        for field in matches {
            effective.merge_from(field.clone());
        }
        Some(effective)
    }

    /// Returns metadata entries keyed by normalized path.
    #[must_use]
    pub fn fields_by_path(&self) -> BTreeMap<String, FieldMetadata> {
        self.fields
            .iter()
            .cloned()
            .map(|field| (field.path.clone(), field))
            .collect()
    }

    /// Returns all normalized secret paths.
    #[must_use]
    pub fn secret_paths(&self) -> Vec<String> {
        self.fields
            .iter()
            .filter(|field| field.secret)
            .map(|field| field.path.clone())
            .collect()
    }
}