
# konfik
A flexible and composable configuration parser for Rust applications that supports multiple sources and formats.
## Features
- 🔧 **Multiple Sources**: Load configuration from files, environment variables, and CLI arguments
- 📁 **Multiple Formats**: Support for JSON, YAML, and TOML configuration files
- 🎯 **Priority System**: CLI args > Environment variables > Config files
- ✅ **Validation**: Custom validation functions for your configuration
- 🚀 **Zero Config**: Works out of the box with sensible defaults
- 📦 **Derive Macro**: Simple `#[derive(Config)]` for easy setup
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
konfik = "0.1"
serde = { version = "1.0", features = ["derive"] }
```
### Basic Usage
```rust
use konfik::{ConfigLoader, LoadConfig, Config};
use serde::Deserialize;
#[derive(Deserialize, Config, Debug)]
struct AppConfig {
database_url: String,
port: u16,
debug: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load with defaults (looks for config.json, config.yaml, config.toml)
let config = AppConfig::load()?;
println!("Config: {:#?}", config);
Ok(())
}
```
### Advanced Configuration
```rust
use konfik::{ConfigLoader, Error, Config};
use serde::Deserialize;
#[derive(Deserialize, Config, Debug)]
struct AppConfig {
database_url: String,
port: u16,
debug: bool,
#[serde(skip)]
runtime_data: Option<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigLoader::default()
.with_env_prefix("MYAPP") // Environment variables: MYAPP_DATABASE_URL, etc.
.with_config_file("app.toml") // Additional config file
.with_cli() // Enable CLI argument parsing
.with_validation(|config| { // Custom validation
if let Some(port) = config.get("port").and_then(|v| v.as_u64()) {
if port > 65535 {
return Err(Error::Validation("Port must be <= 65535".to_string()));
}
}
Ok(())
})
.load::<AppConfig>()?;
println!("Loaded config: {:#?}", config);
Ok(())
}
```
## Configuration Sources & Priority
konfik loads configuration from multiple sources in the following priority order (higher priority overrides lower):
1. **CLI Arguments** (highest priority)
2. **Environment Variables**
3. **Configuration Files** (lowest priority)
### Configuration Files
By default, konfik looks for these files in the current directory:
- `config.json`
- `config.yaml`
- `config.toml`
You can specify custom files:
```rust
let config = ConfigLoader::default()
.with_config_file("custom.toml")
.with_config_file("/etc/myapp/config.yaml")
.load::<AppConfig>()?;
```
### Environment Variables
Environment variables are automatically mapped from your struct fields:
```rust
#[derive(Deserialize, Config)]
struct Config {
database_url: String, // DATABASE_URL
api_key: String, // API_KEY
max_connections: u32, // MAX_CONNECTIONS
}
```
With a prefix:
```rust
let config = ConfigLoader::default()
.with_env_prefix("MYAPP") // MYAPP_DATABASE_URL, MYAPP_API_KEY, etc.
.load::<Config>()?;
```
### CLI Arguments
CLI arguments use kebab-case conversion:
```rust
#[derive(Deserialize, Config)]
struct Config {
database_url: String, // --database-url
max_connections: u32, // --max-connections
debug: bool, // --debug (flag, no value needed)
}
```
Example usage:
```bash
./myapp --database-url "postgres://localhost/db" --max-connections 100 --debug
```
## Supported Types
konfik supports all types that implement `serde::Deserialize`, including:
- Primitives: `bool`, `i32`, `u32`, `f64`, `String`, etc.
- Collections: `Vec<T>`, `HashMap<K, V>`, `BTreeMap<K, V>`, etc.
- Optional values: `Option<T>`
- Nested structs
- Enums
- Custom types with `Deserialize` implementation
### Complex Types from Environment/CLI
Environment variables and CLI arguments can parse complex types:
```bash
# JSON arrays and objects
TAGS='["web", "api", "rust"]'
CONFIG='{"timeout": 30, "retries": 3}'
# CLI
./app --tags '["web", "api"]' --config '{"timeout": 30}'
```
## Validation
Add custom validation logic:
```rust
let config = ConfigLoader::default()
.with_validation(|config| {
// Validate port range
if let Some(port) = config.get("port").and_then(|v| v.as_u64()) {
if !(1024..=65535).contains(&port) {
return Err(Error::Validation("Port must be between 1024 and 65535".into()));
}
}
// Validate required combinations
let has_ssl = config.get("ssl_enabled").and_then(|v| v.as_bool()).unwrap_or(false);
let has_ssl_cert = config.get("ssl_cert_path").and_then(|v| v.as_str()).is_some();
if has_ssl && !has_ssl_cert {
return Err(Error::Validation("SSL enabled but no certificate path provided".into()));
}
Ok(())
})
.load::<AppConfig>()?;
```
## Error Handling
konfik provides detailed error information:
```rust
match AppConfig::load() {
Ok(config) => println!("Config loaded: {:#?}", config),
Err(Error::Io(e)) => eprintln!("Failed to read config file: {}", e),
Err(Error::ConfigParse { type_name, source }) => {
eprintln!("Failed to parse config for {}: {}", type_name, source)
}
Err(Error::Validation(msg)) => eprintln!("Config validation failed: {}", msg),
Err(e) => eprintln!("Config error: {}", e),
}
```
## Examples
### Web Server Configuration
```rust
use konfik::Config;
use serde::Deserialize;
#[derive(Deserialize, Config, Debug)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
database_url: String,
redis_url: Option<String>,
log_level: String,
cors_origins: Vec<String>,
}
// config.toml
// host = "0.0.0.0"
// port = 8080
// workers = 4
// database_url = "postgres://localhost/myapp"
// log_level = "info"
// cors_origins = ["https://example.com"]
// Environment override: PORT=3000
// CLI override: ./server --port 9000 --log-level debug
```
### Database Configuration
```rust
#[derive(Deserialize, Config, Debug)]
struct DatabaseConfig {
url: String,
max_connections: u32,
min_connections: u32,
connection_timeout: u64,
ssl_mode: String,
ssl_cert: Option<String>,
}
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.