# cfgmatic

[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://gitlab.opentc.ru/opentc/rust-cfgmatic)
[![Unsafe](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![rustc](https://img.shields.io/badge/rustc-1.92+-lightgray.svg)](https://www.rust-lang.org)

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

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

## Quick Start

### Basic Environment Configuration

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

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

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

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

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

```bash
# 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:

```bash
# 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

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

```rust
use cfgmatic::ConfigManager;

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

### Conditional Configuration

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

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

## License

This project is licensed under either of:

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

at your option.

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for details.
