pub mod cli; mod log_level; use std::path::PathBuf; use clap::ValueEnum; pub use cli::Cli; pub use cli::Commands; use serde::Deserialize; use tracing_subscriber::EnvFilter; use crate::WardenError; use crate::config::cli::CliEnvironment; use crate::config::cli::database::Database; #[derive(Deserialize, Default, Debug, ValueEnum, Clone, Copy)] #[serde(rename_all = "lowercase")] pub enum Environment { Development, #[default] Production, } impl From for Environment { fn from(value: CliEnvironment) -> Self { match value { CliEnvironment::Dev | CliEnvironment::Development => Self::Development, CliEnvironment::Prod | CliEnvironment::Production => Self::Production, } } } #[derive(Debug, Clone)] pub struct Configuration { pub server: Server, pub database: Database, } #[derive(Debug, Clone)] pub struct Server { pub port: u16, pub environment: Environment, pub log_level: EnvFilter, pub log_dir: PathBuf, pub timeout_secs: u64, } impl Server { fn merge(cli: &Cli, file: &Cli, missing: &mut Vec<&str>) -> Result { let port = cli.server.port.or(file.server.port); if port.is_none() { missing.push("server.port"); } let timeout = cli.server.timeout_secs.or(file.server.timeout_secs); if timeout.is_none() { missing.push("server.timeout"); } let log_dir = cli.server.log_dir.clone().or(file.server.log_dir.clone()); if log_dir.is_none() { missing.push("server.log_dir"); } let log_level = cli .server .log_level .as_ref() .or(file.server.log_level.as_ref()) .map(ToOwned::to_owned); if log_level.is_none() { missing.push("server.log_level"); } let environment = cli.server.environment.or(file.server.environment); if environment.is_none() { missing.push("server.environment"); } if !missing.is_empty() { let err = missing .iter() .map(|f| format!(" - {}", f)) .collect::>() .join("\n"); return Err(WardenError::Config(err)); } let log_level = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { // axum logs rejections from built-in extractors with the `axum::rejection` // target, at `TRACE` level. `axum::rejection=trace` enables showing those events log_level.unwrap().into() }); Ok(Self { port: port.unwrap(), environment: environment.unwrap().into(), log_dir: log_dir.unwrap(), timeout_secs: timeout.unwrap(), log_level, }) } } impl Configuration { pub fn merge(cli: &Cli, file: &Cli) -> Result { let mut missing = Vec::new(); let server = Server::merge(cli, file, &mut missing)?; let database = Database::merge(&cli.database, &file.database)?; Ok(Self { server, database }) } }