cfgmatic-source 5.0.1

Configuration sources (file, env, memory) for cfgmatic framework
Documentation
//! Internal loading pipeline for [`SourceCoordinator`](super::SourceCoordinator).

use std::collections::BTreeMap;

use crate::application::support::load_source_content;
use crate::config::LoadOptions;
use crate::domain::{ParsedContent, Result, SourceError};

use super::entry::SourceEntry;
use super::result::SourceLayer;

pub(super) struct LayerCollection {
    pub(super) layers: Vec<SourceLayer>,
    pub(super) loaded_sources: Vec<String>,
    pub(super) failed_sources: Vec<(String, String)>,
    pub(super) loaded_count: usize,
}

enum SourceLoadAttempt {
    Loaded {
        source_name: String,
        layer: SourceLayer,
    },
    Failed {
        source_name: String,
        error: SourceError,
    },
    SkippedOptional,
}

pub(super) fn collect_loaded_layers(
    sources: &[SourceEntry],
    options: &LoadOptions,
    cache: &mut BTreeMap<String, ParsedContent>,
) -> Result<LayerCollection> {
    let mut layers = Vec::new();
    let mut loaded_sources = Vec::new();
    let mut failed_sources = Vec::new();
    let mut loaded_count = 0;

    for index in sorted_source_indices(sources) {
        match load_attempt(&sources[index], options, cache)? {
            SourceLoadAttempt::Loaded { source_name, layer } => {
                layers.push(layer);
                loaded_sources.push(source_name);
                loaded_count += 1;
            }
            SourceLoadAttempt::Failed { source_name, error } => {
                failed_sources.push((source_name, error.to_string()));
            }
            SourceLoadAttempt::SkippedOptional => {}
        }
    }

    Ok(LayerCollection {
        layers,
        loaded_sources,
        failed_sources,
        loaded_count,
    })
}

fn sorted_source_indices(sources: &[SourceEntry]) -> Vec<usize> {
    let mut indices: Vec<_> = (0..sources.len()).collect();
    indices.sort_by(|left, right| {
        sources[*left]
            .priority
            .cmp(&sources[*right].priority)
            .then_with(|| sources[*left].order.cmp(&sources[*right].order))
    });
    indices
}

fn load_attempt(
    entry: &SourceEntry,
    options: &LoadOptions,
    cache: &mut BTreeMap<String, ParsedContent>,
) -> Result<SourceLoadAttempt> {
    let source_name = entry.source.display_name();
    let cache_key = entry.source.cache_key();

    if options.is_cache_enabled()
        && let Some(cached) = cache.get(&cache_key)
    {
        return Ok(SourceLoadAttempt::Loaded {
            source_name,
            layer: layer_from_entry(entry, cached.clone()),
        });
    }

    match load_source_content(&*entry.source, None) {
        Ok(content) => {
            if options.is_cache_enabled() {
                cache.insert(cache_key, content.clone());
            }
            Ok(SourceLoadAttempt::Loaded {
                source_name,
                layer: layer_from_entry(entry, content),
            })
        }
        Err(error) => {
            if entry.source.is_optional() && options.ignores_missing_optional() {
                Ok(SourceLoadAttempt::SkippedOptional)
            } else if options.is_fail_fast() {
                Err(error)
            } else {
                Ok(SourceLoadAttempt::Failed { source_name, error })
            }
        }
    }
}

fn layer_from_entry(entry: &SourceEntry, content: ParsedContent) -> SourceLayer {
    SourceLayer {
        metadata: entry.source.metadata().with_priority(entry.priority),
        priority: entry.priority,
        registration_index: entry.order,
        content,
    }
}