cfgmatic-source 5.0.1

Configuration sources (file, env, memory) for cfgmatic framework
Documentation
//! Public API tests for loader services.

use cfgmatic_source::{
    CascadeLayer, CascadeLoader, CascadeScope, Format, Loader, MemorySource, MergeStrategy,
};
use serde::Deserialize;
use std::fs;
use tempfile::tempdir;

#[derive(Debug, Deserialize, PartialEq)]
struct AppConfig {
    server: ServerConfig,
}

#[derive(Debug, Deserialize, PartialEq)]
struct ServerConfig {
    host: String,
    port: u16,
}

#[test]
fn test_loader_load_as_from_memory_source() {
    let source = MemorySource::from_json(serde_json::json!({
        "server": {
            "host": "localhost",
            "port": 8080
        }
    }));
    let loader = Loader::new();

    let config: AppConfig = loader
        .load_as(&source)
        .expect("memory source should deserialize");

    assert_eq!(
        config,
        AppConfig {
            server: ServerConfig {
                host: "localhost".to_string(),
                port: 8080,
            },
        }
    );
}

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

    let merged = loader
        .merge(vec![
            cfgmatic_source::ParsedContent::from_json(serde_json::json!({
                "server": {
                    "host": "localhost",
                    "port": 8080
                }
            })),
            cfgmatic_source::ParsedContent::from_json(serde_json::json!({
                "server": {
                    "port": 3000
                }
            })),
        ])
        .expect("shallow merge should succeed");

    let server = merged.get("server").expect("server section should exist");
    assert_eq!(
        server
            .get("port")
            .and_then(cfgmatic_source::ParsedContent::as_integer),
        Some(3000)
    );
    assert!(server.get("host").is_none());
}

#[test]
fn test_cascade_loader_applies_git_like_layering() {
    let temp_dir = tempdir().expect("tempdir should be created");
    let system = temp_dir.path().join("system.toml");
    let global = temp_dir.path().join("global.toml");
    let local = temp_dir.path().join("local.toml");

    fs::write(
        &system,
        r#"
[server]
host = "system.example"
port = 8080
"#,
    )
    .expect("system layer should be written");
    fs::write(
        &global,
        r"
[server]
port = 9000
",
    )
    .expect("global layer should be written");
    fs::write(
        &local,
        r#"
[server]
host = "repo.example"
"#,
    )
    .expect("local layer should be written");

    let loader = CascadeLoader::builder()
        .add_layer(CascadeLayer::new(CascadeScope::System, &system).priority(10))
        .add_layer(CascadeLayer::new(CascadeScope::Global, &global).priority(20))
        .add_layer(CascadeLayer::new(CascadeScope::Local, &local).priority(30))
        .default_format(Format::Toml)
        .build();

    let config: AppConfig = loader.load_as().expect("cascade should deserialize");

    assert_eq!(
        config,
        AppConfig {
            server: ServerConfig {
                host: "repo.example".to_string(),
                port: 9000,
            },
        }
    );
}