#configuration-management #command-line-arguments

gonfig

A unified configuration management library for Rust that seamlessly integrates environment variables, config files, and CLI arguments

10 releases

0.1.12 Jan 11, 2026
0.1.11 Jan 10, 2026
0.1.10 Nov 5, 2025
0.1.7 Sep 25, 2025
0.1.2 Aug 11, 2025

#282 in Configuration


Used in 2 crates (via velora-core)

MIT/Apache

79KB
901 lines

Gonfig

A unified configuration management library for Rust that seamlessly integrates environment variables, configuration files, and CLI arguments with a clean, intuitive API.

Crates.io Documentation License

Features

  • 🎯 Multiple Configuration Sources: Environment variables, config files (JSON/YAML/TOML), and CLI arguments
  • 🔧 Flexible Prefix Management: Configure environment variable prefixes at struct and field levels
  • 🚀 Derive Macro Support: Easy configuration with #[derive(Gonfig)]
  • 🔀 Merge Strategies: Deep merge, replace, or append configurations
  • 🛡️ Type Safety: Fully type-safe configuration with serde
  • ✅ Validation: Built-in validation support for your configurations
  • ⚙️ Granular Control: Enable/disable sources at struct or field level
  • 🚫 Skip Support: Exclude sensitive or runtime fields from configuration
  • 📊 Structured Logging: Built-in tracing support with fine-grained control via RUST_LOG

Quick Start

Add to your Cargo.toml:

[dependencies]
gonfig = "0.1.9"
serde = { version = "1.0", features = ["derive"] }

Basic Example

use gonfig::Gonfig;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Gonfig)]
#[Gonfig(env_prefix = "APP")]
struct Config {
    // Environment variable: APP_DATABASE_URL
    database_url: String,

    // Environment variable: APP_PORT
    port: u16,

    // Skip this field from configuration
    #[skip]
    runtime_client: Option<DatabaseClient>,
}

fn main() -> gonfig::Result<()> {
    std::env::set_var("APP_DATABASE_URL", "postgres://localhost/myapp");
    std::env::set_var("APP_PORT", "8080");

    let config = Config::from_gonfig()?;
    tracing::info!("Database: {}", config.database_url);
    tracing::info!("Port: {}", config.port);
    Ok(())
}

Advanced Example

use gonfig::{Gonfig, ConfigBuilder, MergeStrategy};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Gonfig)]
#[Gonfig(allow_cli, env_prefix = "MD")]
struct Mongo {
    // Environment variable: MD_MONGO_USERNAME
    // CLI argument: --mongo-username
    username: String,

    // Environment variable: MD_MONGO_PASSWORD
    // CLI argument: --mongo-password
    password: String,
}

#[derive(Debug, Serialize, Deserialize, Gonfig)]
struct Application {
    // Environment variable: MD_APP_USERNAME
    username: String,

    // Environment variable: MD_APP_PASSWORD
    password: String,

    #[skip]
    client: Option<HttpClient>, // Excluded from configuration
}

#[derive(Debug, Serialize, Deserialize, Gonfig)]
#[Gonfig(env_prefix = "MD")]
pub struct Config {
    mongo: Mongo,
    app: Application,
}

fn main() -> gonfig::Result<()> {
    // Option 1: Use derive macro (simple)
    let config = Config::from_gonfig()?;

    // Option 2: Use builder (advanced)
    let config = ConfigBuilder::new()
        .with_merge_strategy(MergeStrategy::Deep)
        .with_env("MD")
        .with_file_optional("config.toml")?
        .with_cli()
        .validate_with(|value| {
            // Custom validation logic
            if let Some(port) = value.get("port").and_then(|p| p.as_u64()) {
                if port > 65535 {
                    return Err(gonfig::Error::Validation("Invalid port".into()));
                }
            }
            Ok(())
        })
        .build::<Config>()?;

    Ok(())
}

Environment Variable Naming

Environment variables follow a hierarchical naming pattern:

Pattern: {PREFIX}_{STRUCT}_{FIELD}

#[derive(Gonfig)]
#[Gonfig(env_prefix = "MD")]
struct Config {
    mongo: MongoConfig,  // MD_MONGO_*
    app: AppConfig,      // MD_APP_*
}

struct MongoConfig {
    username: String,    // → MD_MONGO_USERNAME
    password: String,    // → MD_MONGO_PASSWORD
}

Field Overrides

struct Config {
    #[gonfig(env_name = "DATABASE_URL")]
    db_url: String,      // → DATABASE_URL (ignores prefix)

    port: u16,           // → MD_CONFIG_PORT (uses prefix)
}

Derive Attributes

Struct-level Attributes

Attribute Description Example
env_prefix = "PREFIX" Set environment variable prefix #[Gonfig(env_prefix = "APP")]
allow_cli Enable CLI argument support #[Gonfig(allow_cli)]
allow_config Enable config file support #[Gonfig(allow_config)]

Field-level Attributes

Attribute Description Example
env_name = "NAME" Override environment variable name #[gonfig(env_name = "DB_URL")]
cli_name = "name" Override CLI argument name #[gonfig(cli_name = "database-url")]
#[skip] Skip field from all sources #[skip]
#[skip_gonfig] Alternative skip syntax #[skip_gonfig]

Skip Attributes

Use skip attributes to exclude fields from configuration:

#[derive(Gonfig)]
struct Config {
    database_url: String,  // ✅ Included in configuration

    #[skip]
    runtime_client: Option<Client>,  // ❌ Excluded from configuration

    #[skip_gonfig]
    internal_state: Vec<String>,     // ❌ Excluded from configuration
}

Common Skip Use Cases

  1. Non-serializable types: Database connections, thread pools
  2. Runtime state: Caches, temporary data
  3. Sensitive data: API keys loaded from secure vaults
  4. Computed fields: Values calculated from other config
  5. Implementation details: Internal buffers, state machines

CLI Argument Naming

CLI arguments use kebab-case naming:

#[derive(Gonfig)]
#[Gonfig(allow_cli)]
struct Config {
    database_url: String,    // → --database-url
    max_connections: u32,    // → --max-connections

    #[gonfig(cli_name = "db-port")]
    port: u16,               // → --db-port
}

Usage: cargo run -- --database-url postgres://localhost --max-connections 100

Configuration Sources & Priority

Sources are merged with the following priority (higher number wins):

  1. Default values (Priority: 0)
  2. Config files (Priority: 1)
  3. Environment variables (Priority: 2)
  4. CLI arguments (Priority: 3)

Merge Strategies

use gonfig::MergeStrategy;

ConfigBuilder::new()
    .with_merge_strategy(MergeStrategy::Deep)     // Merge nested objects
    .with_merge_strategy(MergeStrategy::Replace)  // Replace entire values
    .with_merge_strategy(MergeStrategy::Append)   // Append arrays

Validation

Add custom validation logic:

ConfigBuilder::new()
    .validate_with(|config| {
        if let Some(port) = config.get("port").and_then(|p| p.as_u64()) {
            if port == 0 || port > 65535 {
                return Err(gonfig::Error::Validation(
                    "Port must be between 1 and 65535".into()
                ));
            }
        }
        Ok(())
    })
    .build::<Config>()?;

Config File Support

Gonfig supports multiple config file formats:

TOML

# config.toml
database_url = "postgres://localhost/prod"
port = 8080

[mongo]
username = "admin"
password = "secret"

YAML

# config.yaml
database_url: postgres://localhost/prod
port: 8080
mongo:
  username: admin
  password: secret

JSON

{
  "database_url": "postgres://localhost/prod",
  "port": 8080,
  "mongo": {
    "username": "admin",
    "password": "secret"
  }
}

Logging and Debugging

Gonfig uses the tracing crate for structured logging. Control logging output using the RUST_LOG environment variable:

# Show all logs (default level: INFO)
RUST_LOG=info cargo run --example simple

# Show only errors
RUST_LOG=error cargo run --example simple

# Show debug information
RUST_LOG=debug cargo run --example simple

# Show trace-level details
RUST_LOG=trace cargo run --example simple

All examples include tracing initialization:

use tracing_subscriber::EnvFilter;

fn main() -> gonfig::Result<()> {
    // Initialize tracing subscriber
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::from_default_env()
                .add_directive(tracing::Level::INFO.into())
        )
        .init();

    // Your code here...
}

This gives you fine-grained control over logging output without cluttering your application logs.

Error Handling

Gonfig provides detailed error types:

use gonfig::Error;

match config_result {
    Err(Error::Environment(msg)) => tracing::error!("Environment error: {}", msg),
    Err(Error::Config(msg)) => tracing::error!("Config file error: {}", msg),
    Err(Error::Cli(msg)) => tracing::error!("CLI error: {}", msg),
    Err(Error::Validation(msg)) => tracing::error!("Validation error: {}", msg),
    Err(Error::Serialization(msg)) => tracing::error!("Serialization error: {}", msg),
    Ok(config) => tracing::info!("Config loaded successfully: {:?}", config),
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

Dependencies

~8–12MB
~143K SLoC