use std::sync::Arc;
pub use request_id::RequestId;
pub mod api;
pub mod auth;
pub mod config;
pub mod error;
pub mod logging;
pub mod mail;
pub mod policy;
pub mod rate_limit;
pub mod sanitize;
pub mod security;
pub mod smtp;
pub mod metrics;
pub mod request_id;
pub mod status;
pub mod status_memory;
#[cfg(feature = "sqlite")]
pub mod status_sqlite;
pub mod validation;
#[cfg(test)]
mod tests;
pub struct AppState {
config_store: arc_swap::ArcSwap<config::AppConfig>,
pub smtp: smtp::SmtpTransport,
pub rate_limiter: Arc<rate_limit::RateLimiter>,
pub metrics: Arc<metrics::Metrics>,
pub status_store: Arc<dyn status::StatusStore>,
}
impl AppState {
pub fn new(config: config::AppConfig) -> Arc<Self> {
let smtp = smtp::build_transport(&config.smtp)
.expect("SMTP transport construction failed after config validation");
let rate_limiter = Arc::new(rate_limit::RateLimiter::new(&config.rate_limit));
let m = Arc::new(metrics::Metrics::new());
let status_store: Arc<dyn status::StatusStore> = if !config.status.enabled {
Arc::new(status_memory::NoopStatusStore)
} else {
match config.status.store.as_str() {
#[cfg(feature = "sqlite")]
"sqlite" => {
let db_path = config.status.db_path.as_deref()
.expect("db_path validated in config::validate_config");
status_sqlite::SqliteStatusStore::open(db_path, &config.status, Arc::clone(&m))
.unwrap_or_else(|e| {
eprintln!("fatal: {e}");
std::process::exit(1);
})
}
_ => status_memory::InMemoryStatusStore::new(&config.status, Arc::clone(&m)),
}
};
Arc::new(Self {
config_store: arc_swap::ArcSwap::from_pointee(config),
smtp,
rate_limiter,
metrics: m,
status_store,
})
}
pub fn config(&self) -> Arc<config::AppConfig> {
self.config_store.load_full()
}
pub fn new_with_store(config: config::AppConfig, store: Arc<dyn status::StatusStore>) -> Arc<Self> {
let smtp = smtp::build_transport(&config.smtp)
.expect("build_transport failed in new_with_store");
let rate_limiter = Arc::new(rate_limit::RateLimiter::new(&config.rate_limit));
let m = Arc::new(metrics::Metrics::new());
Arc::new(Self {
config_store: arc_swap::ArcSwap::from_pointee(config),
smtp,
rate_limiter,
metrics: m,
status_store: store,
})
}
pub fn reload_config(&self, new_config: config::AppConfig) {
self.status_store.reload_config(&new_config.status);
self.config_store.store(Arc::new(new_config));
tracing::info!(event = "config_reloaded");
}
}