/ src / lib.rs
lib.rs
  1  // SPDX-License-Identifier: MIT
  2  // Copyright (c) 2025 kingananas20
  3  
  4  //! # konfik
  5  //!
  6  //! A flexible and composable configuration parser for Rust applications that supports multiple sources and formats.
  7  //!
  8  //! ## Features
  9  //!
 10  //! - 🔧 **Multiple Sources**: Load configuration from files, environment variables, and CLI arguments
 11  //! - 📁 **Multiple Formats**: Support for JSON, YAML, and TOML configuration files
 12  //! - 🎯 **Priority System**: CLI args > Environment variables > Config files
 13  //! - ✅ **Validation**: Custom validation functions for your configuration
 14  //! - 🚀 **Zero Config**: Works out of the box with sensible defaults
 15  //! - 📦 **Derive Macro**: Simple `#[derive(Konfik)]` for easy setup
 16  //!
 17  //! ## Quick Start
 18  //!
 19  //! Add to your `Cargo.toml`:
 20  //!
 21  //! ```toml
 22  //! [dependencies]
 23  //! konfik = "0.1"
 24  //! serde = { version = "1.0", features = ["derive"] }
 25  //! clap = { version = "4.5", features = ["derive"] } # optional! only needed for cli arguments
 26  //! ```
 27  //!
 28  //! ### Basic Usage
 29  //!
 30  //! ```rust
 31  //! use konfik::{ConfigLoader, LoadConfig, Konfik};
 32  //! use serde::Deserialize;
 33  //!
 34  //! #[derive(Deserialize, Konfik, Debug)]
 35  //! struct AppConfig {
 36  //!     database_url: String,
 37  //!     port: u16,
 38  //!     debug: bool,
 39  //! }
 40  //!
 41  //! fn main() -> Result<(), Box<dyn std::error::Error>> {
 42  //!     // Load with defaults (looks for config.json, config.yaml, config.toml)
 43  //!     let config = AppConfig::load()?;
 44  //!
 45  //!     println!("Config: {:#?}", config);
 46  //!     Ok(())
 47  //! }
 48  //! ```
 49  //!
 50  //! ### Advanced Configuration
 51  //!
 52  //! ```rust
 53  //! use konfik::{ConfigLoader, Error, Konfik};
 54  //! use serde::Deserialize;
 55  //! use clap::Parser;
 56  //!
 57  //! #[derive(Deserialize, Konfik, Debug, Parser)]
 58  //! struct AppConfig {
 59  //!     database_url: String,
 60  //!     port: u16,
 61  //!     debug: bool,
 62  //!     #[serde(skip)]
 63  //!     runtime_data: Option<String>,
 64  //! }
 65  //!
 66  //! fn main() -> Result<(), Box<dyn std::error::Error>> {
 67  //!     let config = ConfigLoader::default()
 68  //!         .with_env_prefix("MYAPP")           // Environment variables: MYAPP_DATABASE_URL, etc.
 69  //!         .with_config_file("app.toml")       // Additional config file
 70  //!         .with_cli()                         // Enable CLI argument parsing
 71  //!         .with_validation(|config| {         // Custom validation
 72  //!             if let Some(port) = config.get("port").and_then(|v| v.as_u64()) {
 73  //!                 if port > 65535 {
 74  //!                     return Err(Error::Validation("Port must be <= 65535".to_string()));
 75  //!                 }
 76  //!             }
 77  //!             Ok(())
 78  //!         })
 79  //!         .load::<AppConfig>()?;
 80  //!
 81  //!     println!("Loaded config: {:#?}", config);
 82  //!     Ok(())
 83  //! }
 84  //! ```
 85  //!
 86  //! ## Configuration Sources & Priority
 87  //!
 88  //! konfik loads configuration from multiple sources in the following priority order (higher priority overrides lower):
 89  //!
 90  //! 1. **CLI Arguments** (highest priority)
 91  //! 2. **Environment Variables**
 92  //! 3. **Configuration Files** (lowest priority)
 93  //!
 94  //! ### Configuration Files
 95  //!
 96  //! By default, konfik looks for these files in the current directory:
 97  //!
 98  //! - `config.json`
 99  //! - `config.yaml`
100  //! - `config.toml`
101  //!
102  //! You can specify custom files:
103  //!
104  //! ```rust
105  //! let config = ConfigLoader::default()
106  //!     .with_config_file("custom.toml")
107  //!     .with_config_files(&["/etc/myapp/config.yaml", "config.json"])
108  //!     .load::<AppConfig>()?;
109  //! ```
110  //!
111  //! ### Environment Variables
112  //!
113  //! Environment variables are automatically mapped from your struct fields:
114  //!
115  //! ```rust
116  //! #[derive(Deserialize, Konfik)]
117  //! struct Config {
118  //!     database_url: String,  // DATABASE_URL
119  //!     api_key: String,       // API_KEY
120  //!     max_connections: u32,  // MAX_CONNECTIONS
121  //! }
122  //! ```
123  //!
124  //! With a prefix:
125  //!
126  //! ```rust
127  //! let config = ConfigLoader::default()
128  //!     .with_env_prefix("MYAPP")  // MYAPP_DATABASE_URL, MYAPP_API_KEY, etc.
129  //!     .load::<Config>()?;
130  //! ```
131  //!
132  //! ### CLI Arguments
133  //!
134  //! The CLI is integrated with `clap`. It detects at runtime which fields are still
135  //! missing and makes those required in the CLI:
136  //!
137  //! ```rust
138  //! #[derive(Deserialize, Konfik)]
139  //! struct Konfik {
140  //!     database_url: String,  // --database-url
141  //!     max_connections: u32,  // --max-connections
142  //!     debug: bool,          // --debug (flag, no value needed)
143  //! }
144  //! ```
145  //!
146  //! ## Supported Types
147  //!
148  //! `Konfik` supports all types.
149  //!
150  //! ## Validation
151  //!
152  //! Add custom validation logic:
153  //!
154  //! ```rust
155  //! let config = ConfigLoader::default()
156  //!     .with_validation(|config| {
157  //!         // Validate port range
158  //!         if let Some(port) = config.get("port").and_then(|v| v.as_u64()) {
159  //!             if !(1024..=65535).contains(&port) {
160  //!                 return Err(Error::Validation("Port must be between 1024 and 65535".into()));
161  //!             }
162  //!         }
163  //!
164  //!         // Validate required combinations
165  //!         let has_ssl = config.get("ssl_enabled").and_then(|v| v.as_bool()).unwrap_or(false);
166  //!         let has_ssl_cert = config.get("ssl_cert_path").and_then(|v| v.as_str()).is_some();
167  //!
168  //!         if has_ssl && !has_ssl_cert {
169  //!             return Err(Error::Validation("SSL enabled but no certificate path provided".into()));
170  //!         }
171  //!
172  //!         Ok(())
173  //!     })
174  //!     .load::<AppConfig>()?;
175  //! ```
176  
177  mod config_loader;
178  pub mod config_meta;
179  mod error;
180  
181  pub use config_loader::ConfigLoader;
182  pub use error::Error;
183  pub use konfik_derive::{Konfik, Nested};
184  
185  /// Simple trait for loading configuration
186  pub trait LoadConfig: Sized {
187      /// Load configuration from all available sources
188      ///
189      /// # Errors
190      ///
191      /// Both functions return an `Error` if any of the following occur:
192      ///
193      /// 1. **File I/O errors** – if reading configuration files fails (for `load_with`, this depends on the provided `ConfigLoader`).
194      /// 2. **Deserialization errors** – if converting the loaded configuration into `Self` fails.
195      /// 3. **Validation errors** – if any validation defined in the loader fails.
196      /// 4. **Other loader-specific errors** – any errors returned by the custom `ConfigLoader` in `load_with`.
197      fn load() -> Result<Self, Error>;
198  }