feature/api-setup #4

Merged
GW_MC merged 11 commits from feature/api-setup into master 2025-12-02 17:25:47 +08:00
8 changed files with 249 additions and 221 deletions
Showing only changes of commit 537737b1cc - Show all commits

View File

@@ -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<Self, String>;
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<Self, String> {
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<Self, String> {
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<Self, String> {
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<Self, String> {
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")
}

76
apps/api/src/configs.rs Normal file
View File

@@ -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<Self, String>;
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<Self, String> {
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")
}

View File

@@ -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<Self, String> {
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(())
}
}

View File

@@ -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";

View File

@@ -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<Self, String> {
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(())
}
}

View File

@@ -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<Self, String> {
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(())
}
}

View File

@@ -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() {

View File

@@ -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<dyn std::error::Error>> {
// Here you can add any startup tasks you want to run when the application starts.