cfgmatic 2.1.0

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, validation, and Terraform-like plan/apply workflow.

Overview

cfgmatic is a facade crate that re-exports functionality from specialized sub-crates, providing a unified, type-safe API for configuration management:

graph TB
    subgraph "cfgmatic (Facade)"
        C[cfgmatic]
    end

    subgraph "Sub-crates"
        P[cfgmatic-paths<br/>Path discovery]
        F[cfgmatic-files<br/>File loading]
        M[cfgmatic-macros<br/>Derive macros]
        PL[cfgmatic-plan<br/>Plan/Apply workflow]
    end

    C --> P
    C --> F
    C --> M
    C --> PL

Crate Overview

Crate Purpose Status
cfgmatic Facade - re-exports all crates Active
cfgmatic-paths Platform-specific path discovery (XDG, Windows, macOS) Active
cfgmatic-files File discovery, parsing, merging, watching Active
cfgmatic-macros Derive macros (Config, ConfigEnv) Active
cfgmatic-plan Terraform-like plan/apply workflow Active
cfgmatic-state Legacy state management Deprecated

Features

  • default: Enables files, env, and plan features
  • files: File-based configuration loading (via cfgmatic-files)
  • env: Environment variable support with derive macros (via cfgmatic-macros)
  • plan: Terraform-like plan/apply workflow (via cfgmatic-plan)
  • reactive: Reactive configuration with tokio streams

Installation

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

Quick Start

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<()> {
    let config = AppConfig::from_env()?;
    println!("Server: {}:{}", config.host, config.port);
    Ok(())
}

Plan/Apply Workflow

Use cfgmatic-plan for Terraform-like state management:

use cfgmatic::plan::prelude::*;
use serde::{Deserialize, Serialize};

// Define a resource
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Server {
    name: String,
    port: u16,
    host: String,
}

impl Resource for Server {
    fn id(&self) -> &str { &self.name }
    fn resource_type() -> &'static str { "server" }
}

// Implement a handler
struct ServerHandler;

impl CrudHandler<Server> for ServerHandler {
    fn create(&self, resource: &Server) -> Result<()> {
        println!("Creating server: {}:{}", resource.host, resource.port);
        Ok(())
    }

    fn update(&self, old: &Server, new: &Server) -> Result<()> {
        println!("Updating server: {} -> {}:{}", old.port, new.host, new.port);
        Ok(())
    }

    fn delete(&self, resource: &Server) -> Result<()> {
        println!("Deleting server: {}", resource.name);
        Ok(())
    }
}

fn main() -> Result<()> {
    // Current state
    let current = vec![Server { name: "api".into(), port: 8080, host: "localhost".into() }];

    // Desired state
    let desired = vec![
        Server { name: "api".into(), port: 3000, host: "0.0.0.0".into() },
        Server { name: "web".into(), port: 80, host: "0.0.0.0".into() },
    ];

    // Create plan
    let plan = GlobalPlan::from_resources(current, desired)?;

    // Display plan
    println!("{}", plan.display());
    // Plan: 1 to add, 1 to change, 0 to destroy.

    // Apply with handler
    let handler = ServerHandler;
    let result = plan.apply(|resource_type, _resource_id, plan| {
        if resource_type == "server" {
            if let Some(resource_plan) = plan.downcast_ref::<ResourcePlan<Server>>() {
                match resource_plan {
                    ResourcePlan::Create { resource } => handler.create(resource)?,
                    ResourcePlan::Update { old, new, .. } => handler.update(old, new)?,
                    ResourcePlan::Delete { resource } => handler.delete(resource)?,
                    ResourcePlan::NoChange { .. } => {}
                }
            }
        }
        Ok(())
    })?;

    println!("Applied: {}, Errors: {}", result.applied_count(), result.error_count());
    Ok(())
}

NPM-style Environment Variables

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

# Flat structure
export MYAPP__PORT=3000
export MYAPP__HOST=0.0.0.0

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

Using Individual Crates

You can use sub-crates directly for more granular control:

cfgmatic-paths

use cfgmatic_paths::PathsBuilder;

let finder = PathsBuilder::new("myapp").build();
let config_path = finder.preferred_config_path();

cfgmatic-files

use cfgmatic_files::{FileFinder, Format};

let finder = FileFinder::new("myapp");
let config = finder.load()?;

cfgmatic-plan

use cfgmatic_plan::prelude::*;

let plan = Planner::plan(current, desired)?;
let result = Executor::new().execute(&plan, handler)?;

Migration from cfgmatic-state

cfgmatic-state is deprecated. Migrate to cfgmatic-plan:

// Old (deprecated)
use cfgmatic_state::{CurrentState, DesiredState, Plan};

// New - via cfgmatic facade
use cfgmatic::plan::{CurrentState, DesiredState, Plan, GlobalPlan};

// Or directly
use cfgmatic_plan::prelude::*;

Feature Flags

Feature Description Default
files File-based configuration Yes
env Environment variable support Yes
plan Terraform-like plan/apply Yes
reactive Config subscriptions/watching No

Examples

See examples/ directory for complete working examples:

# Environment configuration
cargo run --example basic_env

# Plan/Apply workflow
cargo run --example plan_apply

# Reactive configuration
cargo run --example reactive --features reactive

License

MIT OR Apache-2.0