confy-rs 0.1.0

Simple, efficient configuration loading: multi-format (TOML/YAML/JSON), layered merging, and hot reloading
Documentation

confy-rs

CI crates.io docs.rs License: MIT Rust

English | 简体中文

Simple, efficient configuration loading for Rust — multi-format, layered merging, and hot reloading.

Features

  • Multi-format — TOML / YAML / JSON, auto-detected from the file extension
  • Layered — in-memory defaults plus any number of files, deep-merged in order
  • Type-safe — deserializes straight into your own structs via serde
  • Hot reload — debounced cross-platform file watching; readers are lock-free, and a failed reload keeps the previous configuration active
  • Lean — a handful of small dependencies, everything non-essential is feature-gated, no async runtime required

Installation

[dependencies]
confy-rs = "0.1"

TOML and hot reloading are enabled by default. YAML is opt-in:

confy-rs = { version = "0.1", features = ["yaml"] }

Quick start

use serde::Deserialize;

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

fn main() -> confy_rs::Result<()> {
    // Format is detected from the extension (.toml / .yaml / .yml / .json)
    let config: AppConfig = confy_rs::load("config.toml")?;
    println!("{} listening on {}", config.name, config.port);
    Ok(())
}

Layered configuration

Sources are merged in the order they are added — later sources override earlier ones. Objects merge recursively; scalars, arrays and null replace the previous value entirely.

let config: AppConfig = confy_rs::builder()
    .defaults(serde_json::json!({ "port": 8080 }))? // lowest priority
    .file("config/default.toml")                    // required, error if missing
    .file_optional("config/local.yaml")             // silently skipped if missing
    .build()?;

Hot reloading

let watcher = confy_rs::builder()
    .file("config.toml")
    .watch::<AppConfig>()?;

watcher.on_change(|config| println!("port is now {}", config.port));
watcher.on_error(|err| eprintln!("reload failed, old config kept: {err}"));

let current = watcher.get(); // Arc<AppConfig>, lock-free

What you get:

  • Fail fast — the initial load is synchronous; a broken config errors at startup, not later
  • Editor-friendly — parent directories are watched, so atomic-rename saves (vim, VS Code, ...) are handled
  • Debounced — bursts of file events collapse into a single reload; unchanged content triggers no callbacks
  • Resilient — a failed reload never crashes or corrupts state: the previous config stays active and the error is reported via on_error
  • Fast readswatcher.get() is lock-free (arc-swap), cheap enough for per-request use
  • Runtime-agnostic — one background thread, no tokio required; call get() from async code freely

Cargo features

Feature Default Effect
toml TOML support via the toml crate
yaml YAML support via serde_yaml_ng
watch hot reloading via notify + arc-swap

JSON support is always built in.

Examples

cargo run --example basic       # one-line loading
cargo run --example layered     # defaults + cross-format layering
cargo run --example hot_reload  # edit fixtures/config.toml and watch it reload

MSRV

Rust 1.96.0 (pinned by rust-toolchain.toml).

Development

cargo install cargo-nextest --locked  # test runner
cargo install --locked cargo-deny     # dependency / license audit
cargo install typos-cli               # spell check
pip install pre-commit && pre-commit install

Common commands:

cargo nextest run --all-features                          # tests
cargo clippy --all-targets --all-features -- -D warnings  # lints
cargo fmt                                                 # format

See CONTRIBUTING.md for the full workflow.

License

Licensed under MIT.