diff --git a/Cargo.lock b/Cargo.lock index b82b302..0c97647 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -107,7 +118,7 @@ dependencies = [ "serde", "serde_derive", "unicode-ident", - "winnow 1.0.1", + "winnow", ] [[package]] @@ -225,7 +236,7 @@ dependencies = [ "aes-gcm", "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "chrono", "cookie", @@ -259,6 +270,12 @@ dependencies = [ "sqlx", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -280,6 +297,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -409,6 +432,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -422,7 +464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "aes-gcm", - "base64", + "base64 0.22.1", "percent-encoding", "rand 0.8.5", "subtle", @@ -581,6 +623,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "dotenvy" version = "0.15.7" @@ -882,6 +930,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1049,7 +1106,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1310,6 +1367,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1343,7 +1411,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags", + "bitflags 2.11.0", "libc", "plain", "redox_syscall 0.7.4", @@ -1360,6 +1428,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "litemap" version = "0.8.2" @@ -1425,6 +1499,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "1.2.0" @@ -1436,6 +1516,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -1500,7 +1590,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "getrandom 0.2.17", "http", @@ -1532,6 +1622,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "parking" version = "2.2.1" @@ -1561,6 +1661,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1576,6 +1682,49 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1829,7 +1978,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -1838,7 +1987,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -1847,7 +1996,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "http", @@ -1885,7 +2034,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -1933,6 +2082,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + [[package]] name = "rsa" version = "0.9.10" @@ -1953,6 +2113,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2077,7 +2247,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2154,15 +2324,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2286,7 +2447,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "chrono", "crc", @@ -2363,8 +2524,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", - "bitflags", + "base64 0.22.1", + "bitflags 2.11.0", "byteorder", "bytes", "chrono", @@ -2407,8 +2568,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", - "bitflags", + "base64 0.22.1", + "bitflags 2.11.0", "byteorder", "chrono", "crc", @@ -2525,7 +2686,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2700,45 +2861,13 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tower" version = "0.5.3" @@ -2761,7 +2890,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -2839,6 +2968,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicase" version = "2.9.0" @@ -3069,7 +3204,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -3131,13 +3266,13 @@ dependencies = [ "axum", "axum_session", "axum_session_sqlx", + "config", "oauth2", "reqwest 0.13.2", "serde", "sqlx", "thiserror 2.0.18", "tokio", - "toml", "tower-http", ] @@ -3518,15 +3653,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "1.0.1" @@ -3594,7 +3720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "indexmap", "log", "serde", @@ -3630,6 +3756,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yoke" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 41728e9..2be745b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,5 @@ tokio = { version = "1.0", features = ["full"] } thiserror = "2.0" anyhow = "1.0" serde = { version = "1.0", features = ["derive"] } -toml = "0.8" +config = { version = "0.13", features = ["toml"] } tower-http = { version = "0.6", features = ["fs", "cors"] } diff --git a/src/config.rs b/src/config.rs index 6e4d484..4d8f21e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,4 @@ use serde::{Deserialize, Serialize}; -use std::fs; use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -36,54 +35,57 @@ pub struct SessionConfig { } impl Config { - /// Load configuration from a TOML file - /// Falls back to environment variables if file doesn't exist + /// Load configuration from a TOML file and environment variables + /// config-rs merges file config with environment variables automatically pub fn load(path: &str) -> Result> { + let mut builder = config::Config::builder(); + + // Add default values first + let defaults = Self::defaults(); + builder = builder.add_source( + config::Config::try_from(&defaults) + .map_err(|e| Box::new(e) as Box)?, + ); + + // Load from file if it exists if Path::new(path).exists() { - let content = fs::read_to_string(path)?; - let config: Config = toml::from_str(&content)?; - Ok(config) - } else { - // Fall back to environment variables - Ok(Config::from_env()) + builder = builder.add_source(config::File::with_name(path).required(false)); } + + // Override with environment variables + // Environment variables should be prefixed with the app name and use __ for nesting + // e.g., WEIGHT_TRACKER_OIDC__CLIENT_ID for oidc.client_id + builder = builder.add_source( + config::Environment::with_prefix("WEIGHT_TRACKER") + .try_parsing(true) + .separator("__"), + ); + + let config = builder.build()?; + let result: Config = config.try_deserialize()?; + Ok(result) } - /// Load configuration from environment variables - pub fn from_env() -> Self { - let client_id = - std::env::var("OIDC_CLIENT_ID").unwrap_or_else(|_| "your_client_id".to_string()); - let client_secret = std::env::var("OIDC_CLIENT_SECRET") - .unwrap_or_else(|_| "your_client_secret".to_string()); - let auth_url = std::env::var("OIDC_AUTH_URL") - .unwrap_or_else(|_| "https://your-provider.com/auth".to_string()); - let token_url = std::env::var("OIDC_TOKEN_URL") - .unwrap_or_else(|_| "https://your-provider.com/token".to_string()); - let redirect_url = std::env::var("OIDC_REDIRECT_URL") - .unwrap_or_else(|_| "http://localhost:3000/auth/callback".to_string()); - let host = std::env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); - let port = std::env::var("SERVER_PORT") - .unwrap_or_else(|_| "3000".to_string()) - .parse() - .unwrap_or(3000); - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "sqlite:weight_tracker.db".to_string()); - let session_secret = std::env::var("SESSION_SECRET").unwrap_or_else(|_| { - "your_secret_key_that_is_long_enough_so_the_library_does_not_complain".to_string() - }); - + /// Get default configuration values + fn defaults() -> Self { Config { oidc: OidcConfig { - client_id, - client_secret, - auth_url, - token_url, - redirect_url, + client_id: "your_client_id".to_string(), + client_secret: "your_client_secret".to_string(), + auth_url: "https://your-provider.com/auth".to_string(), + token_url: "https://your-provider.com/token".to_string(), + redirect_url: "http://localhost:3000/auth/callback".to_string(), + }, + server: ServerConfig { + host: "127.0.0.1".to_string(), + port: 3000, + }, + database: DatabaseConfig { + url: "sqlite:weight_tracker.db".to_string(), }, - server: ServerConfig { host, port }, - database: DatabaseConfig { url: database_url }, session: SessionConfig { - secret: session_secret, + secret: "your_secret_key_that_is_long_enough_so_the_library_does_not_complain" + .to_string(), }, } } diff --git a/src/main.rs b/src/main.rs index cba8642..a256d1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,11 @@ use weight_tracker::{AppState, create_app, config::Config}; #[tokio::main] async fn main() { // Load configuration from config.toml or environment variables - let config = Config::load("config.toml").unwrap_or_else(|_| { - println!("config.toml not found, using environment variables"); - Config::from_env() + // config-rs automatically merges file + environment + defaults + let config = Config::load("config.toml").unwrap_or_else(|e| { + eprintln!("Failed to load configuration: {}", e); + eprintln!("Using default values. Set environment variables prefixed with WEIGHT_TRACKER_ to override."); + std::process::exit(1); }); // Set up database