use std::fmt::Display; use secrecy::{ExposeSecret, Secret}; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Settings { pub database: DatabaseSettings, pub application: ApplicationSettings, } #[derive(Debug, Deserialize)] pub struct DatabaseSettings { pub username: String, pub password: Secret, pub port: u16, pub host: String, pub database_name: String, } #[derive(Debug, Deserialize)] pub struct ApplicationSettings { pub port: u16, pub host: String, } impl DatabaseSettings { pub fn to_string_no_db(&self) -> Secret { Secret::new(format!( "postgres://{}:{}@{}:{}", self.username, self.password.expose_secret(), self.host, self.port )) } pub fn to_string(&self) -> Secret { Secret::new(format!( "postgres://{}:{}@{}:{}/{}", self.username, self.password.expose_secret(), self.host, self.port, self.database_name )) } } pub fn get_config() -> Result { let base_path = std::env::current_dir().expect("Failed to determine current directory"); let config_directory = base_path.join("config"); let env: Environment = std::env::var("APP_ENVIRONMENT") .unwrap_or_else(|_| "local".into()) .try_into() .expect("Failed to parse APP_ENVIRONMENT"); let env_filename = format!("{}.toml", &env); let settings = config::Config::builder() .add_source(config::File::from(config_directory.join("base.toml"))) .add_source(config::File::from(config_directory.join(env_filename))) .build()?; settings.try_deserialize::() } #[derive(Debug)] pub enum Environment { Local, Production, } impl Display for Environment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Environment::Local => write!(f, "local"), Environment::Production => write!(f, "production"), } } } impl TryFrom for Environment { type Error = String; fn try_from(value: String) -> Result { match value.to_lowercase().as_str() { "local" => Ok(Self::Local), "production" => Ok(Self::Production), other => Err(format!( "{} is not supported environment. \ Use either `local` or `production`.", other )), } } }