cfgmatic-source 5.0.1

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

use serde::de::DeserializeOwned;

use crate::config::MergeStrategy;
use crate::domain::{Format, ParsedContent, RawContent, Result, Source, SourceError};

use super::Loader;
use crate::application::SourceLayer;
use crate::application::support::{
    load_source_content, merge_contents, parse_raw_as_format, parse_raw_content,
};

impl Loader {
    /// Load raw content from a source.
    ///
    /// # Errors
    ///
    /// Returns an error if the source cannot be read.
    pub fn load_raw<S: Source>(&self, source: &S) -> Result<RawContent> {
        source.validate()?;
        source.load_raw()
    }

    /// Load and parse content from a source.
    ///
    /// # Errors
    ///
    /// Returns an error if loading or parsing fails.
    pub fn load<S: Source>(&self, source: &S) -> Result<ParsedContent> {
        load_source_content(source, self.default_format)
    }

    /// Load and parse one source into a canonical layer.
    ///
    /// # Errors
    ///
    /// Returns an error if loading or parsing fails.
    pub fn load_layer<S: Source>(&self, source: &S) -> Result<SourceLayer> {
        let metadata = source.metadata();
        Ok(SourceLayer {
            priority: metadata.priority,
            metadata,
            registration_index: 0,
            content: self.load(source)?,
        })
    }

    /// Load multiple sources into canonical layers.
    ///
    /// Registration indices are assigned in slice order.
    ///
    /// # Errors
    ///
    /// Returns an error if loading or parsing fails.
    pub fn load_layers<S: Source>(&self, sources: &[S]) -> Result<Vec<SourceLayer>> {
        sources
            .iter()
            .enumerate()
            .map(|(registration_index, source)| {
                let metadata = source.metadata();
                Ok(SourceLayer {
                    priority: metadata.priority,
                    metadata,
                    registration_index,
                    content: self.load(source)?,
                })
            })
            .collect()
    }

    /// Load and parse content from a source into a specific type.
    ///
    /// # Errors
    ///
    /// Returns an error if loading, parsing, or deserialization fails.
    pub fn load_as<S: Source, T: DeserializeOwned>(&self, source: &S) -> Result<T> {
        let content = self.load(source)?;
        self.to_type(content)
    }

    /// Parse raw content using a specific format.
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails.
    pub fn parse(&self, raw: &RawContent, format: Format) -> Result<ParsedContent> {
        parse_raw_as_format(raw, format)
    }

    /// Parse raw content, detecting the format.
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails or format cannot be detected.
    pub fn parse_raw(
        &self,
        raw: &RawContent,
        detected_format: Option<Format>,
    ) -> Result<ParsedContent> {
        parse_raw_content(raw, detected_format, self.default_format)
    }

    /// Merge multiple parsed contents.
    ///
    /// Uses the merge strategy from options and preserves strategy semantics,
    /// including strict conflict detection.
    ///
    /// # Errors
    ///
    /// Returns an error if the selected merge strategy rejects conflicting input.
    pub fn merge(&self, contents: Vec<ParsedContent>) -> Result<ParsedContent> {
        merge_contents(contents, self.merge_strategy())
    }

    /// Convert parsed content to a specific type.
    ///
    /// # Errors
    ///
    /// Returns an error if deserialization fails.
    pub fn to_type<T: DeserializeOwned>(&self, content: ParsedContent) -> Result<T> {
        content.into_type()
    }

    /// Load from multiple sources and merge.
    ///
    /// Sources are loaded in order and merged according to the merge strategy.
    /// Optional sources that fail are skipped if `ignore_optional_missing` is true.
    ///
    /// # Errors
    ///
    /// Returns an error if a required source fails and `fail_fast` is true.
    pub fn load_multiple<S: Source>(&self, sources: &[S]) -> Result<ParsedContent> {
        let mut contents = Vec::new();
        let mut errors: Vec<(String, SourceError)> = Vec::new();

        for source in sources {
            match self.load(source) {
                Ok(content) => contents.push(content),
                Err(error) => {
                    if source.is_optional() && self.options.ignores_missing_optional() {
                        continue;
                    }
                    if self.options.is_fail_fast() {
                        return Err(error);
                    }
                    errors.push((source.display_name(), error));
                }
            }
        }

        if !errors.is_empty() && contents.is_empty() {
            let error_messages: Vec<String> = errors
                .into_iter()
                .map(|(name, error)| format!("{name}: {error}"))
                .collect();
            return Err(SourceError::custom(&error_messages.join(", ")));
        }

        self.merge(contents)
    }

    /// Read the active merge strategy from loader options.
    const fn merge_strategy(&self) -> MergeStrategy {
        self.options.merge_strategy
    }
}