stratify 1.1.0

Layered configuration and structured logging for Rust services: pluggable sources, priority merging, typed access, and a tracing facade
Documentation
use stratify::config::Builder;

#[tokio::test]
async fn end_to_end_multiple_sources() {
    let base = tempfile::NamedTempFile::new().unwrap();
    let overrides = tempfile::NamedTempFile::new().unwrap();

    std::fs::write(
        base.path(),
        r#"{"host": "localhost", "port": 5432, "debug": false}"#,
    )
    .unwrap();
    std::fs::write(
        overrides.path(),
        r#"{"host": "prod.example.com", "debug": true}"#,
    )
    .unwrap();

    let store = Builder::default()
        .json(base.path(), 100)
        .json(overrides.path(), 50)
        .build()
        .await
        .unwrap();

    assert_eq!(store.get_str("host").unwrap(), "prod.example.com");
    assert_eq!(store.get_u64("port").unwrap(), 5432);
    assert!(store.get_bool("debug").unwrap());
}

#[tokio::test]
async fn deep_merge_nested_objects() {
    let base = tempfile::NamedTempFile::new().unwrap();
    let overrides = tempfile::NamedTempFile::new().unwrap();

    std::fs::write(
        base.path(),
        r#"{"db": {"host": "localhost", "port": 5432, "pool": 10}}"#,
    )
    .unwrap();
    std::fs::write(
        overrides.path(),
        r#"{"db": {"host": "db.prod.local", "tls": true}}"#,
    )
    .unwrap();

    let store = Builder::default()
        .json(base.path(), 100)
        .json(overrides.path(), 50)
        .build()
        .await
        .unwrap();

    assert_eq!(store.get_str("db.host").unwrap(), "db.prod.local");
    assert_eq!(store.get_u64("db.port").unwrap(), 5432);
    assert_eq!(store.get_u64("db.pool").unwrap(), 10);
    assert!(store.get_bool("db.tls").unwrap());
}

#[tokio::test]
async fn deserialize_struct() {
    use serde::Deserialize;

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

    let f = tempfile::NamedTempFile::new().unwrap();
    std::fs::write(
        f.path(),
        r#"{"app": {"name": "hermes", "log_level": "debug"}}"#,
    )
    .unwrap();

    let store = Builder::default().json(f.path(), 0).build().await.unwrap();

    let cfg: AppConfig = store.get("app").unwrap();
    assert_eq!(cfg.name, "hermes");
    assert_eq!(cfg.log_level, "debug");
}

#[tokio::test]
async fn missing_key_is_none() {
    let f = tempfile::NamedTempFile::new().unwrap();
    std::fs::write(f.path(), r#"{"exists": true}"#).unwrap();

    let store = Builder::default().json(f.path(), 0).build().await.unwrap();

    assert!(store.get_str("missing").is_none());
    assert!(store.get_u64("missing").is_none());
    assert!(store.get_bool("missing").is_none());
}

#[tokio::test]
async fn empty_builder_produces_empty_store() {
    let store = Builder::default().build().await.unwrap();
    assert!(store.get_str("anything").is_none());
}