cfgmatic 0.1.4

High-level configuration management framework for Rust with derive macros and validation
Documentation

cfgmatic

License Unsafe rustc

High-level configuration management framework for Rust with derive macros, environment variable support, and validation.

Overview

cfgmatic provides a unified, type-safe API for configuration management with support for:

  • Declarative Configuration: Derive macros for automatic config loading
  • Environment Variables: NPM-style naming (MYAPP__DATABASE__URL)
  • Built-in Validation: Integration with validator crate
  • File-based Configs: Support for TOML, JSON, YAML formats
  • Reactive Configuration: Optional reactive streams for config changes
  • Cross-platform: Works on Linux, macOS, and Windows

Features

  • default: Enables files and env features
  • files: File-based configuration loading (via cfgmatic-files)
  • env: Environment variable support with derive macros (via cfgmatic-macros)
  • reactive: Reactive configuration with tokio streams

Installation

[dependencies]
cfgmatic = "0.1"
serde = { version = "1", features = ["derive"] }

Quick Start

Basic Environment Configuration

use cfgmatic::{Config, ConfigLoader};
use serde::Deserialize;

#[derive(Debug, Deserialize, Config)]
#[config(prefix = "MYAPP")]
struct AppConfig {
    #[config(env = "PORT", default = "8080")]
    port: u16,

    #[config(env = "HOST", default = "localhost")]
    host: String,
}

fn main() -> anyhow::Result<()> {
    // Set environment variables:
    // export MYAPP__PORT=3000
    // export MYAPP__HOST=0.0.0.0

    let config = AppConfig::from_env()?;
    println!("Server: {}:{}", config.host, config.port);
    Ok(())
}

With Validation

use cfgmatic::{Config, ConfigLoader};
use serde::Deserialize;
use validator::Validate;

#[derive(Debug, Deserialize, Config, Validate)]
#[config(prefix = "MYAPP")]
struct DatabaseConfig {
    #[validate(url)]
    #[config(env = "DATABASE_URL")]
    url: String,

    #[validate(range(min = 1, max = 100))]
    #[config(default = "10")]
    pool_size: u32,
}

fn main() -> anyhow::Result<()> {
    let config = DatabaseConfig::from_env()?;

    if let Err(errors) = config.validate() {
        eprintln!("Validation errors: {:?}", errors);
    }

    Ok(())
}

Nested Structures

use cfgmatic::{Config, ConfigLoader};
use serde::Deserialize;

#[derive(Debug, Deserialize, Config)]
#[config(prefix = "MYAPP")]
struct AppConfig {
    #[config(nested)]
    server: ServerConfig,

    #[config(nested)]
    database: DatabaseConfig,
}

#[derive(Debug, Deserialize, Config)]
struct ServerConfig {
    #[config(default = "8080")]
    port: u16,

    #[config(default = "localhost")]
    host: String,
}

#[derive(Debug, Deserialize, Config)]
struct DatabaseConfig {
    #[config(default = "postgres://localhost/db")]
    url: String,

    #[config(default = "10")]
    pool_size: u32,
}

// Environment variables:
// export MYAPP__SERVER__PORT=3000
// export MYAPP__SERVER__HOST=0.0.0.0
// export MYAPP__DATABASE__URL=postgres://localhost/mydb
// export MYAPP__DATABASE__POOL_SIZE=20

File + Environment Loading

use cfgmatic::{Config, ConfigManager};
use serde::Deserialize;

#[derive(Debug, Deserialize, Config, Default)]
#[config(prefix = "MYAPP")]
struct AppConfig {
    #[config(default = "8080")]
    port: u16,

    #[config(default = "localhost")]
    host: String,
}

fn main() -> anyhow::Result<()> {
    // Load from files first, then override with environment
    let config: AppConfig = ConfigManager::new("myapp")
        .with_env_prefix("MYAPP")
        .load()?;

Reactive Configuration

use cfgmatic::reactive::{ConfigQuery, ConfigStream};
use tokio::stream::StreamExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Subscribe to configuration changes
    let mut stream = ConfigStream::new("myapp")
        .subscribe()?;

    while let Some(change) = stream.next().await {
        println!("Config changed: {:?}", change);
    }

    Ok(())
}

NPM-style Environment Variables

Environment variables use double underscore (__) as a separator for nested fields:

# Flat structure
export MYAPP__PORT=3000
export MYAPP__HOST=0.0.0.0

# Nested structure
export MYAPP__SERVER__PORT=3000
export MYAPP__SERVER__HOST=0.0.0.0
export MYAPP__DATABASE__URL=postgres://localhost/db
export MYAPP__DATABASE__POOL_SIZE=20

Configuration Derive Macro Attributes

Struct-level Attributes

  • #[config(prefix = "PREFIX")]: Set environment variable prefix
  • #[config(nested)]: Mark as nested structure (don't use prefix)

Field-level Attributes

  • #[config(env = "NAME")]: Custom environment variable name
  • #[config(default = "value")]: Default value (must be string)
  • #[config(nested)]: Nest field with prefix separator

Examples

The examples/ directory contains complete working examples:

# Basic environment configuration
cargo run --example basic_env

# Complete example with all features
MYAPP__APP_NAME="MyApp" MYAPP__SERVER__PORT=8080 cargo run --example complete

# Custom prefix
MYAPP__PORT=3000 cargo run --example custom_prefix

# Environment loader
cargo run --example env_loader

# File configuration
cargo run --example file_config

# Nested configuration
MYAPP__SERVER__PORT=3000 cargo run --example nested_config

# Reactive configuration
cargo run --example reactive

# With validation
MYAPP__PORT=8080 cargo run --example with_validation

Advanced Usage

Custom Validation

use validator::{Validate, ValidationError};

#[derive(Debug, Deserialize, Config, Validate)]
#[config(prefix = "MYAPP")]
struct Config {
    #[validate(custom = "validate_port")]
    port: u16,
}

fn validate_port(port: &u16) -> Result<(), ValidationError> {
    if *port < 1024 {
        return Err(ValidationError::new("must be >= 1024"));
    }
    Ok(())
}

Multiple Configuration Sources

use cfgmatic::ConfigManager;

let config: AppConfig = ConfigManager::new("myapp")
    .with_env_prefix("MYAPP")
    .with_file("config/production.toml")?
    .load()?;

Conditional Configuration

#[derive(Debug, Deserialize, Config)]
#[config(prefix = "MYAPP")]
struct AppConfig {
    #[config(env = "MODE", default = "development")]
    mode: String,

    #[config(env = "DEBUG", default = "false")]
    debug: bool,
}

Feature Flags

Default Features

  • files: Enable file-based configuration via cfgmatic-files
  • env: Enable environment variable support via cfgmatic-macros

Optional Features

  • reactive: Enable reactive configuration streams (requires tokio)

Relationship to Other Crates

cfgmatic is part of a modular configuration framework:

  • cfgmatic-paths: Platform-specific path discovery
  • cfgmatic-files: File discovery and parsing
  • cfgmatic-macros: Proc macros for derive macros (internal, not published)
  • cfgmatic: High-level configuration framework (this crate)

Performance Considerations

  • Configuration loading is one-time at startup
  • Environment variable parsing is cached
  • File loading uses efficient deserialization
  • Reactive configuration uses tokio channels for minimal overhead

Error Handling

All functions return Result<T, Error> where Error is:

pub enum Error {
    Env(String),
    Io(std::io::Error),
    Parse(String),
    Validation(validator::ValidationErrors),
}

License

This project is licensed under either of:

at your option.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.