cfgmatic-source 5.0.1

Configuration sources (file, env, memory) for cfgmatic framework
Documentation
//! Service methods for [`CascadeLoader`](super::CascadeLoader).

use std::time::Instant;

use serde::de::DeserializeOwned;

use crate::application::support::{elapsed_millis_u64, load_source_content, merge_contents};
use crate::domain::{Result, SourceError};
use crate::infrastructure::FileSource;

use super::{CascadeLoadResult, CascadeLoader, ResolvedCascadeLayer};

impl CascadeLoader {
    /// Load and merge all layers.
    ///
    /// # Errors
    ///
    /// Returns an error when a required layer cannot be read or merge rules fail.
    pub fn load(&self) -> Result<CascadeLoadResult> {
        let start = Instant::now();
        let mut ordered_layers = self.layers.clone();

        ordered_layers.sort_by(|left, right| {
            left.priority
                .cmp(&right.priority)
                .then_with(|| left.path.cmp(&right.path))
        });

        let mut contents = Vec::new();
        let mut loaded_layers = Vec::new();
        let mut skipped_layers = Vec::new();
        let mut failed_layers = Vec::new();

        for layer in &ordered_layers {
            let layer_info = ResolvedCascadeLayer::from(layer);
            if layer.optional && !layer.path.exists() {
                skipped_layers.push(layer_info);
                continue;
            }

            let source = FileSource::new(layer.path.clone());

            match load_source_content(&source, layer.format.or(self.default_format)) {
                Ok(content) => {
                    contents.push(content);
                    loaded_layers.push(layer_info);
                }
                Err(error) if layer.optional && error.is_not_found() => {
                    skipped_layers.push(layer_info);
                }
                Err(error) if self.options.is_fail_fast() => return Err(error),
                Err(error) => failed_layers.push((layer_info, error.to_string())),
            }
        }

        if contents.is_empty() && !failed_layers.is_empty() {
            let messages = failed_layers
                .iter()
                .map(|(layer, error)| format!("{}:{}: {error}", layer.scope, layer.path.display()))
                .collect::<Vec<_>>()
                .join(", ");
            return Err(SourceError::custom(&messages));
        }

        let content = merge_contents(contents, self.options.merge_strategy)?;

        Ok(CascadeLoadResult {
            content,
            loaded_layers,
            skipped_layers,
            failed_layers,
            processing_time_ms: elapsed_millis_u64(start),
        })
    }

    /// Load and deserialize all layers.
    ///
    /// # Errors
    ///
    /// Returns an error if loading or deserialization fails.
    pub fn load_as<T: DeserializeOwned>(&self) -> Result<T> {
        self.load()?.to_type()
    }
}