gonfig

Crates.iogonfig
lib.rsgonfig
version0.1.7
created_at2025-08-03 18:09:11.090099+00
updated_at2025-09-25 18:51:11.705373+00
descriptionA unified configuration management library for Rust that seamlessly integrates environment variables, config files, and CLI arguments
homepagehttps://github.com/itsparser/gonfig
repositoryhttps://github.com/itsparser/gonfig
max_upload_size
id1779934
size150,895
itsparser (itsparser)

documentation

https://docs.rs/gonfig

README

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

Quick Start

Add to your Cargo.toml:

[dependencies]
gonfig = "0.1.6"
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()?;
    println!("Database: {}", config.database_url);
    println!("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"
  }
}

Examples

See the examples/ directory for more comprehensive examples:

Run examples:

cargo run --example your_usecase
cargo run --example skip_attributes

Error Handling

Gonfig provides detailed error types:

use gonfig::Error;

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

Contributing

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

License

This project is licensed under either of

at your option.# gonfig

Commit count: 52

cargo fmt