use std::net::SocketAddr; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct Config { pub listen_addr: SocketAddr, pub jwt_secret: String, pub database_url: String, } impl Config { pub fn builder() -> ConfigBuilder { ConfigBuilder::default() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigBuilder { listen_addr: Option, jwt_secret: Option, database_url: Option, } impl ConfigBuilder { pub fn file(self) -> Result { let file = std::env::args() .nth(1) .unwrap_or("/etc/unnamed_server.toml".to_string()); match std::fs::read_to_string(file) { Ok(s) => Ok(self.merge(toml::from_str(&s)?)), Err(err) => { tracing::warn!("Error reading config file: {err}"); Ok(self) } } } pub fn env(self) -> Self { let _ = dotenvy::dotenv(); self.merge(Self { listen_addr: std::env::var("LISTEN_ADDR").ok(), jwt_secret: std::env::var("JWT_SECRET").ok(), database_url: std::env::var("DATABASE_URL").ok(), }) } /// Merge self with other, overwriting any existing values on self with other's. fn merge(self, other: Self) -> Self { Self { listen_addr: other.listen_addr.or(self.listen_addr), jwt_secret: other.jwt_secret.or(self.jwt_secret), database_url: other.database_url.or(self.database_url), } } pub fn build(self) -> Result { let err = |s: &str| format!("Config error: missing value: {s}"); Ok(Config { listen_addr: self .listen_addr .and_then(|s| s.parse().ok()) .ok_or_else(|| err("listen_addr"))?, jwt_secret: self.jwt_secret.ok_or_else(|| err("jwt_secret"))?, database_url: self.database_url.ok_or_else(|| err("database_url"))?, }) } } impl Default for ConfigBuilder { fn default() -> Self { Self { listen_addr: Some("127.0.0.1:30000".to_string()), jwt_secret: None, database_url: None, } } }