gonfig 0.1.6

A unified configuration management library for Rust that seamlessly integrates environment variables, config files, and CLI arguments
Documentation
# 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](https://img.shields.io/crates/v/gonfig.svg)](https://crates.io/crates/gonfig)
[![Documentation](https://docs.rs/gonfig/badge.svg)](https://docs.rs/gonfig)
[![License](https://img.shields.io/crates/l/gonfig.svg)](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`:

```toml
[dependencies]
gonfig = "0.1"
serde = { version = "1.0", features = ["derive"] }
```

### Basic Example

```rust
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

```rust
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}`

```rust
#[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

```rust
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:

```rust
#[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:

```rust
#[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

```rust
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:

```rust
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
```toml
# config.toml
database_url = "postgres://localhost/prod"
port = 8080

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

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

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

## Examples

See the [examples/](examples/) directory for more comprehensive examples:

- [`your_usecase.rs`]examples/your_usecase.rs - Your exact use case implementation
- [`skip_attributes.rs`]examples/skip_attributes.rs - Comprehensive skip examples
- [`madara_usecase.rs`]examples/madara_usecase.rs - Complex hierarchical configuration
- [`simple.rs`]examples/simple.rs - Basic usage example

Run examples:
```bash
cargo run --example your_usecase
cargo run --example skip_attributes
```

## Error Handling

Gonfig provides detailed error types:

```rust
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

 * Apache License, Version 2.0, ([LICENSE-APACHE]LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT]LICENSE-MIT or https://opensource.org/licenses/MIT)

at your option.# gonfig