From 537737b1cc53b5fa46d634e717aebd49fbb92f41 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:59:40 +0800 Subject: [PATCH] refactor configs into a crate --- apps/api/src/config.rs | 218 ------------------------------- apps/api/src/configs.rs | 76 +++++++++++ apps/api/src/configs/database.rs | 53 ++++++++ apps/api/src/configs/key.rs | 9 ++ apps/api/src/configs/logging.rs | 52 ++++++++ apps/api/src/configs/server.rs | 56 ++++++++ apps/api/src/main.rs | 4 +- apps/api/src/tasks/startup.rs | 2 +- 8 files changed, 249 insertions(+), 221 deletions(-) delete mode 100644 apps/api/src/config.rs create mode 100644 apps/api/src/configs.rs create mode 100644 apps/api/src/configs/database.rs create mode 100644 apps/api/src/configs/key.rs create mode 100644 apps/api/src/configs/logging.rs create mode 100644 apps/api/src/configs/server.rs diff --git a/apps/api/src/config.rs b/apps/api/src/config.rs deleted file mode 100644 index 745dca6..0000000 --- a/apps/api/src/config.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::net::IpAddr; - -use config::{Config, ConfigError}; -use tracing::{Level, debug, error, warn}; - -const LOGGING_LEVEL_KEY: &str = "LOGGING.LEVEL"; -const LOGGING_UTC_KEY: &str = "LOGGING.UTC"; -const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS"; -const SERVER_PORT_KEY: &str = "SERVER.PORT"; -const DATABASE_URL_KEY: &str = "DATABASE.URL"; -const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS"; -const DATABASE_MIGRATE_ON_STARTUP_KEY: &str = "DATABASE.MIGRATION.MIGRATE_ON_STARTUP"; - -trait FromConfig: Sized { - fn from_config(config: &Config) -> Result; - fn validate(&self) -> Result<(), String>; -} - -#[derive(Debug, Clone)] -pub struct ProgramSettings { - pub logging: LoggingSettings, - pub database: DatabaseSettings, - pub server: ServerSettings, -} - -#[derive(Debug, Clone)] -pub struct LoggingSettings { - pub level: Level, - pub utc: bool, -} - -#[derive(Debug, Clone)] -pub struct DatabaseSettings { - pub url: String, - pub max_connections: u32, - pub migrate_on_startup: bool, -} - -#[derive(Debug, Clone)] -pub struct ServerSettings { - pub address: IpAddr, - pub port: u16, -} - -impl FromConfig for ProgramSettings { - fn from_config(_config: &Config) -> Result { - let config = ProgramSettings { - logging: LoggingSettings::from_config(_config)?, - database: DatabaseSettings::from_config(_config)?, - server: ServerSettings::from_config(_config)?, - }; - config.validate()?; - Ok(config) - } - - fn validate(&self) -> Result<(), String> { - self.logging.validate()?; - self.database.validate()?; - self.server.validate()?; - Ok(()) - } -} - -impl FromConfig for LoggingSettings { - fn from_config(_config: &Config) -> Result { - const DEFAULT_LOGGING_LEVEL: Level = Level::INFO; - Ok(LoggingSettings { - level: _config - .get_string(LOGGING_LEVEL_KEY) - .unwrap_or_else(|err| { - warn!( - "Failed to read {} from configuration, defaulting to {}. Error: {}", - LOGGING_LEVEL_KEY, DEFAULT_LOGGING_LEVEL, err - ); - DEFAULT_LOGGING_LEVEL.to_string() - }) - .parse() - .unwrap_or_else(|err| { - warn!( - "Invalid logging level in configuration, defaulting to {}. Error: {}", - DEFAULT_LOGGING_LEVEL, err - ); - DEFAULT_LOGGING_LEVEL - }), - utc: _config - .get_bool(LOGGING_UTC_KEY) - .unwrap_or_else(|err: ConfigError| { - const DEFAULT_UTC: bool = false; - warn!( - "Invalid UTC setting in configuration, defaulting to {}. Error: {}", - DEFAULT_UTC, err - ); - DEFAULT_UTC - }), - }) - } - - fn validate(&self) -> Result<(), String> { - Ok(()) - } -} - -impl FromConfig for DatabaseSettings { - fn from_config(_config: &Config) -> Result { - Ok(DatabaseSettings { - url: _config - .get_string(DATABASE_URL_KEY) - .map_err(|op| match op { - ConfigError::NotFound(_) => "Database URL not found in configuration".into(), - err => { - format!("Failed to read Database URL from configuration {err}") - } - })?, - max_connections: _config - .get_int(DATABASE_MAX_CONNECTIONS_KEY) - .unwrap_or_else(|err| { - const DEFAULT_MAX_CONNECTIONS: i64 = 10; - warn!( - "{} not set or invalid in configuration, defaulting to {}. Error: {}", - DATABASE_MAX_CONNECTIONS_KEY, DEFAULT_MAX_CONNECTIONS, err - ); - DEFAULT_MAX_CONNECTIONS - }) as u32, - migrate_on_startup: _config - .get_bool(DATABASE_MIGRATE_ON_STARTUP_KEY) - .unwrap_or_else(|err| { - const DEFAULT_MIGRATE_ON_STARTUP: bool = true; - warn!( - "{} not set or invalid in configuration, defaulting to {}. Error: {}", - DATABASE_MIGRATE_ON_STARTUP_KEY, DEFAULT_MIGRATE_ON_STARTUP, err - ); - DEFAULT_MIGRATE_ON_STARTUP - }), - }) - } - - fn validate(&self) -> Result<(), String> { - Ok(()) - } -} - -impl FromConfig for ServerSettings { - fn from_config(_config: &Config) -> Result { - Ok(ServerSettings { - address: _config - .get_string(SERVER_ADDRESS_KEY) - .unwrap_or_else(|err| { - const DEFAULT_ADDRESS: &str = "0.0.0.0"; - match err { - ConfigError::NotFound(_) => {} - _ => { - warn!( - "Failed to read {} from configuration, defaulting to {}. Error: {}", - SERVER_ADDRESS_KEY, DEFAULT_ADDRESS, err - ); - } - }; - DEFAULT_ADDRESS.to_string() - }) - .parse() - .map_err(|e| format!("Invalid {} in configuration: {}", SERVER_ADDRESS_KEY, e))?, - - port: _config.get_int(SERVER_PORT_KEY).unwrap_or_else(|err| { - const DEFAULT_PORT: i64 = 8080; - warn!( - "{} not set or invalid in configuration, defaulting to {}. Error: {}", - SERVER_PORT_KEY, DEFAULT_PORT, err - ); - DEFAULT_PORT - }) as u16, - }) - } - - fn validate(&self) -> Result<(), String> { - #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)] - if self.port == 0 || self.port > 65535 { - return Err("Server port must be between 1 and 65535".into()); - } - Ok(()) - } -} - -pub fn get_program_settings() -> ProgramSettings { - debug!("Loading program settings from configuration sources"); - let settings = Config::builder() - // dev / generated config has the highest priority (Overwrite by user config files) - .add_source(config::File::with_name("generated-config.yaml").required(false)) - // user config files - .add_source( - config::File::with_name("/etc/yet-another-nginx-proxy-manager/config").required(false), - ) - .add_source( - config::File::with_name("$HOME/.config/yet-another-nginx-proxy-manager/config") - .required(false), - ) - .add_source(config::File::with_name("config.yaml").required(false)) - // environment variables have the highest priority (Overwrite all config files) - .add_source( - config::Environment::with_prefix("YANPM") - .separator("__") - .prefix_separator("_"), - ) - .build() - .expect("Failed to build configuration"); - - debug!("Configuration sources loaded successfully"); - debug!("Parsing program settings from configuration"); - - ProgramSettings::from_config(&settings) - .inspect_err(|err| { - error!("Configuration error: {}", err); - debug!("Current configurations: {:#?}", settings); - }) - .inspect(|_| { - debug!("Program settings parsed successfully"); - }) - .expect("Failed to load program settings from configuration") -} diff --git a/apps/api/src/configs.rs b/apps/api/src/configs.rs new file mode 100644 index 0000000..cae85de --- /dev/null +++ b/apps/api/src/configs.rs @@ -0,0 +1,76 @@ +pub mod database; +pub mod logging; +pub mod server; + +mod key; + +use config::Config; +use tracing::{debug, error}; + +pub trait FromConfig: Sized { + fn from_config(config: &Config) -> Result; + fn validate(&self) -> Result<(), String>; +} + +#[derive(Debug, Clone)] +pub struct ProgramSettings { + pub logging: logging::LoggingSettings, + pub database: database::DatabaseSettings, + pub server: server::ServerSettings, +} + +impl FromConfig for ProgramSettings { + fn from_config(_config: &Config) -> Result { + let config = ProgramSettings { + logging: logging::LoggingSettings::from_config(_config)?, + database: database::DatabaseSettings::from_config(_config)?, + server: server::ServerSettings::from_config(_config)?, + }; + config.validate()?; + Ok(config) + } + + fn validate(&self) -> Result<(), String> { + self.logging.validate()?; + self.database.validate()?; + self.server.validate()?; + Ok(()) + } +} + +pub fn get_program_settings() -> ProgramSettings { + debug!("Loading program settings from configuration sources"); + let settings = Config::builder() + // dev / generated config has the highest priority (Overwrite by user config files) + .add_source(config::File::with_name("generated-config.yaml").required(false)) + // user config files + .add_source( + config::File::with_name("/etc/yet-another-nginx-proxy-manager/config").required(false), + ) + .add_source( + config::File::with_name("$HOME/.config/yet-another-nginx-proxy-manager/config") + .required(false), + ) + .add_source(config::File::with_name("config.yaml").required(false)) + // environment variables have the highest priority (Overwrite all config files) + .add_source( + config::Environment::with_prefix("YANPM") + .separator("__") + .prefix_separator("_"), + ) + .build() + .expect("Failed to build configuration"); + + debug!("Configuration sources loaded successfully"); + debug!("Parsing program settings from configuration"); + + ProgramSettings::from_config(&settings) + .inspect_err(|err| { + error!("Configuration error: {}", err); + debug!("Current configurations: {:#?}", settings); + }) + .inspect(|_| { + debug!("Program settings parsed successfully"); + }) + .expect("Failed to load program settings from configuration") +} diff --git a/apps/api/src/configs/database.rs b/apps/api/src/configs/database.rs new file mode 100644 index 0000000..25d9a78 --- /dev/null +++ b/apps/api/src/configs/database.rs @@ -0,0 +1,53 @@ +use config::{Config, ConfigError}; +use tracing::warn; + +use super::{ + FromConfig, + key::{DATABASE_MAX_CONNECTIONS_KEY, DATABASE_MIGRATE_ON_STARTUP_KEY}, +}; + +#[derive(Debug, Clone)] +pub struct DatabaseSettings { + pub url: String, + pub max_connections: u32, + pub migrate_on_startup: bool, +} + +impl FromConfig for DatabaseSettings { + fn from_config(_config: &Config) -> Result { + Ok(DatabaseSettings { + url: _config + .get_string(super::key::DATABASE_URL_KEY) + .map_err(|op| match op { + ConfigError::NotFound(_) => "Database URL not found in configuration".into(), + err => { + format!("Failed to read Database URL from configuration {err}") + } + })?, + max_connections: _config + .get_int(DATABASE_MAX_CONNECTIONS_KEY) + .unwrap_or_else(|err| { + const DEFAULT_MAX_CONNECTIONS: i64 = 10; + warn!( + "{} not set or invalid in configuration, defaulting to {}. Error: {}", + DATABASE_MAX_CONNECTIONS_KEY, DEFAULT_MAX_CONNECTIONS, err + ); + DEFAULT_MAX_CONNECTIONS + }) as u32, + migrate_on_startup: _config + .get_bool(DATABASE_MIGRATE_ON_STARTUP_KEY) + .unwrap_or_else(|err| { + const DEFAULT_MIGRATE_ON_STARTUP: bool = true; + warn!( + "{} not set or invalid in configuration, defaulting to {}. Error: {}", + DATABASE_MIGRATE_ON_STARTUP_KEY, DEFAULT_MIGRATE_ON_STARTUP, err + ); + DEFAULT_MIGRATE_ON_STARTUP + }), + }) + } + + fn validate(&self) -> Result<(), String> { + Ok(()) + } +} diff --git a/apps/api/src/configs/key.rs b/apps/api/src/configs/key.rs new file mode 100644 index 0000000..dd31902 --- /dev/null +++ b/apps/api/src/configs/key.rs @@ -0,0 +1,9 @@ +pub(crate) const LOGGING_LEVEL_KEY: &str = "LOGGING.LEVEL"; +pub(crate) const LOGGING_UTC_KEY: &str = "LOGGING.UTC"; +// +pub(crate) const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS"; +pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT"; +// +pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL"; +pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS"; +pub(crate) const DATABASE_MIGRATE_ON_STARTUP_KEY: &str = "DATABASE.MIGRATION.MIGRATE_ON_STARTUP"; diff --git a/apps/api/src/configs/logging.rs b/apps/api/src/configs/logging.rs new file mode 100644 index 0000000..1aa47b9 --- /dev/null +++ b/apps/api/src/configs/logging.rs @@ -0,0 +1,52 @@ +use config::{Config, ConfigError}; +use tracing::{Level, warn}; + +use super::{ + FromConfig, + key::{LOGGING_LEVEL_KEY, LOGGING_UTC_KEY}, +}; + +#[derive(Debug, Clone)] +pub struct LoggingSettings { + pub level: Level, + pub utc: bool, +} + +impl FromConfig for LoggingSettings { + fn from_config(_config: &Config) -> Result { + const DEFAULT_LOGGING_LEVEL: Level = Level::INFO; + Ok(LoggingSettings { + level: _config + .get_string(LOGGING_LEVEL_KEY) + .unwrap_or_else(|err| { + warn!( + "Failed to read {} from configuration, defaulting to {}. Error: {}", + LOGGING_LEVEL_KEY, DEFAULT_LOGGING_LEVEL, err + ); + DEFAULT_LOGGING_LEVEL.to_string() + }) + .parse() + .unwrap_or_else(|err| { + warn!( + "Invalid logging level in configuration, defaulting to {}. Error: {}", + DEFAULT_LOGGING_LEVEL, err + ); + DEFAULT_LOGGING_LEVEL + }), + utc: _config + .get_bool(LOGGING_UTC_KEY) + .unwrap_or_else(|err: ConfigError| { + const DEFAULT_UTC: bool = false; + warn!( + "Invalid UTC setting in configuration, defaulting to {}. Error: {}", + DEFAULT_UTC, err + ); + DEFAULT_UTC + }), + }) + } + + fn validate(&self) -> Result<(), String> { + Ok(()) + } +} diff --git a/apps/api/src/configs/server.rs b/apps/api/src/configs/server.rs new file mode 100644 index 0000000..16e6bee --- /dev/null +++ b/apps/api/src/configs/server.rs @@ -0,0 +1,56 @@ +use std::net::IpAddr; + +use config::{Config, ConfigError}; +use tracing::warn; + +use super::{ + FromConfig, + key::{SERVER_ADDRESS_KEY, SERVER_PORT_KEY}, +}; + +#[derive(Debug, Clone)] +pub struct ServerSettings { + pub address: IpAddr, + pub port: u16, +} + +impl FromConfig for ServerSettings { + fn from_config(_config: &Config) -> Result { + Ok(ServerSettings { + address: _config + .get_string(SERVER_ADDRESS_KEY) + .unwrap_or_else(|err| { + const DEFAULT_ADDRESS: &str = "0.0.0.0"; + match err { + ConfigError::NotFound(_) => {} + _ => { + warn!( + "Failed to read {} from configuration, defaulting to {}. Error: {}", + SERVER_ADDRESS_KEY, DEFAULT_ADDRESS, err + ); + } + }; + DEFAULT_ADDRESS.to_string() + }) + .parse() + .map_err(|e| format!("Invalid {} in configuration: {}", SERVER_ADDRESS_KEY, e))?, + + port: _config.get_int(SERVER_PORT_KEY).unwrap_or_else(|err| { + const DEFAULT_PORT: i64 = 8080; + warn!( + "{} not set or invalid in configuration, defaulting to {}. Error: {}", + SERVER_PORT_KEY, DEFAULT_PORT, err + ); + DEFAULT_PORT + }) as u16, + }) + } + + fn validate(&self) -> Result<(), String> { + #[allow(clippy::absurd_extreme_comparisons, unused_comparisons)] + if self.port == 0 || self.port > 65535 { + return Err("Server port must be between 1 and 65535".into()); + } + Ok(()) + } +} diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs index 9529a15..6bcbb78 100644 --- a/apps/api/src/main.rs +++ b/apps/api/src/main.rs @@ -1,4 +1,4 @@ -mod config; +mod configs; mod routes; mod tasks; @@ -7,7 +7,7 @@ use database::{ConnectOptions, get_connection}; use tracing::{debug, info}; use tracing_subscriber::fmt::format::{DefaultFields, Format}; -use crate::config::{LoggingSettings, ProgramSettings, get_program_settings}; +use crate::configs::{ProgramSettings, get_program_settings, logging::LoggingSettings}; #[tokio::main] async fn main() { diff --git a/apps/api/src/tasks/startup.rs b/apps/api/src/tasks/startup.rs index d4f1629..17d150b 100644 --- a/apps/api/src/tasks/startup.rs +++ b/apps/api/src/tasks/startup.rs @@ -1,7 +1,7 @@ use migration::migrate_database; use tracing::{debug, info}; -use crate::config::ProgramSettings; +use crate::configs::ProgramSettings; pub async fn run_startup_tasks(config: &ProgramSettings) -> Result<(), Box> { // Here you can add any startup tasks you want to run when the application starts.