cfgmatic-source 5.0.1

Configuration sources (file, env, memory) for cfgmatic framework
Documentation
use std::collections::BTreeMap;

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

use super::Loader;

/// Test source implementation.
struct TestSource {
    content: String,
    format: Format,
    optional: bool,
}

impl TestSource {
    fn new(content: &str, format: Format) -> Self {
        Self {
            content: content.to_string(),
            format,
            optional: false,
        }
    }

    fn with_optional(mut self, optional: bool) -> Self {
        self.optional = optional;
        self
    }
}

impl Source for TestSource {
    fn kind(&self) -> SourceKind {
        SourceKind::Memory
    }

    fn metadata(&self) -> SourceMetadata {
        SourceMetadata::new("test")
    }

    fn load_raw(&self) -> Result<RawContent> {
        Ok(RawContent::from_string(&self.content))
    }

    fn detect_format(&self) -> Option<Format> {
        Some(self.format)
    }

    fn is_optional(&self) -> bool {
        self.optional
    }
}

#[test]
fn test_loader_new() {
    let loader = Loader::new();
    assert_eq!(loader.options().merge_strategy, MergeStrategy::Deep);
}

#[test]
fn test_loader_builder() {
    let loader = Loader::builder()
        .merge_strategy(MergeStrategy::Replace)
        .fail_fast(false)
        .build();

    assert_eq!(loader.options().merge_strategy, MergeStrategy::Replace);
    assert!(!loader.options().is_fail_fast());
}

#[test]
fn test_loader_load_raw() {
    let source = TestSource::new(r#"key = "value""#, Format::Toml);
    let loader = Loader::new();

    let raw = loader.load_raw(&source).unwrap();
    assert!(!raw.is_empty());
}

#[test]
fn test_loader_load() {
    let source = TestSource::new(r#"key = "value""#, Format::Toml);
    let loader = Loader::new();

    let content = loader.load(&source).unwrap();
    assert!(content.is_object());
}

#[test]
fn test_loader_load_layer() {
    let source = TestSource::new(r#"{"key": "value"}"#, Format::Json);
    let loader = Loader::new();

    let layer = loader.load_layer(&source).unwrap();

    assert_eq!(layer.metadata.display_id(), "test");
    assert_eq!(layer.registration_index, 0);
    assert_eq!(layer.content.get("key").unwrap().as_str(), Some("value"));
}

#[test]
fn test_loader_load_layers() {
    let source1 = TestSource::new(r#"{"a": 1}"#, Format::Json);
    let source2 = TestSource::new(r#"{"b": 2}"#, Format::Json);
    let loader = Loader::new();

    let layers = loader.load_layers(&[source1, source2]).unwrap();

    assert_eq!(layers.len(), 2);
    assert_eq!(layers[0].registration_index, 0);
    assert_eq!(layers[1].registration_index, 1);
    assert_eq!(layers[0].content.get("a").unwrap().as_integer(), Some(1));
    assert_eq!(layers[1].content.get("b").unwrap().as_integer(), Some(2));
}

#[test]
fn test_loader_parse() {
    let raw = RawContent::from_string(r#"{"key": "value"}"#);
    let loader = Loader::new();

    let content = loader.parse(&raw, Format::Json).unwrap();
    assert!(content.is_object());
}

#[test]
fn test_loader_merge_empty() {
    let loader = Loader::new();
    let result = loader.merge(vec![]).unwrap();
    assert!(result.is_null());
}

#[test]
fn test_loader_merge_single() {
    let loader = Loader::new();
    let mut obj = BTreeMap::new();
    obj.insert(
        "key".to_string(),
        ParsedContent::String("value".to_string()),
    );
    let content = ParsedContent::Object(obj);

    let result = loader.merge(vec![content.clone()]).unwrap();
    assert_eq!(result, content);
}

#[test]
fn test_loader_merge_multiple() {
    let loader = Loader::builder()
        .merge_strategy(MergeStrategy::Deep)
        .build();

    let mut obj1 = BTreeMap::new();
    obj1.insert("a".to_string(), ParsedContent::Integer(1));

    let mut obj2 = BTreeMap::new();
    obj2.insert("b".to_string(), ParsedContent::Integer(2));

    let result = loader
        .merge(vec![
            ParsedContent::Object(obj1),
            ParsedContent::Object(obj2),
        ])
        .unwrap();

    assert!(result.get("a").is_some());
    assert!(result.get("b").is_some());
}

#[test]
fn test_loader_merge_shallow() {
    let loader = Loader::builder()
        .merge_strategy(MergeStrategy::Shallow)
        .build();

    let nested_low = ParsedContent::from_json(serde_json::json!({
        "section": {
            "host": "localhost",
            "port": 8080
        }
    }));
    let nested_high = ParsedContent::from_json(serde_json::json!({
        "section": {
            "port": 3000
        }
    }));

    let result = loader.merge(vec![nested_low, nested_high]).unwrap();
    let section = result.get("section").unwrap();

    assert_eq!(section.get("port").unwrap().as_integer(), Some(3000));
    assert!(section.get("host").is_none());
}

#[test]
fn test_loader_merge_strict_conflict() {
    let loader = Loader::builder()
        .merge_strategy(MergeStrategy::Strict)
        .build();

    let result = loader.merge(vec![
        ParsedContent::from_json(serde_json::json!({ "port": 8080 })),
        ParsedContent::from_json(serde_json::json!({ "port": 3000 })),
    ]);

    assert!(result.is_err());
}

#[test]
fn test_loader_to_type() {
    use serde::Deserialize;

    #[derive(Debug, Deserialize, PartialEq)]
    struct Config {
        name: String,
    }

    let loader = Loader::new();

    let mut obj = BTreeMap::new();
    obj.insert(
        "name".to_string(),
        ParsedContent::String("test".to_string()),
    );
    let content = ParsedContent::Object(obj);

    let config: Config = loader.to_type(content).unwrap();
    assert_eq!(config.name, "test");
}

#[test]
fn test_loader_load_multiple() {
    let source1 = TestSource::new(r#"{"a": 1}"#, Format::Json);
    let source2 = TestSource::new(r#"{"b": 2}"#, Format::Json);

    let loader = Loader::builder()
        .merge_strategy(MergeStrategy::Deep)
        .build();

    let result = loader.load_multiple(&[source1, source2]).unwrap();
    assert!(result.get("a").is_some());
    assert!(result.get("b").is_some());
}

#[test]
fn test_loader_load_multiple_with_optional() {
    let source1 = TestSource::new(r#"{"a": 1}"#, Format::Json);
    let source2 = TestSource::new(r"invalid", Format::Toml).with_optional(true);

    let loader = Loader::builder().fail_fast(false).build();

    let result = loader.load_multiple(&[source1, source2]).unwrap();
    assert!(result.get("a").is_some());
}

#[test]
fn test_loader_default_format() {
    let source = TestSource::new(r#"{"key": "value"}"#, Format::Unknown);

    let loader = Loader::builder().default_format(Format::Json).build();

    let content = loader.load(&source).unwrap();
    assert!(content.is_object());
}