use std::sync::OnceLock;
use noema::core::{Container, Injectable};
#[derive(Debug, Clone)]
pub struct HttpConfig {
pub host: String,
pub port: u16,
pub env: Environment,
}
impl HttpConfig {
pub fn bind_addr(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
impl Default for HttpConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".into(),
port: 8080,
env: Environment::Production,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Environment {
Development,
Production,
}
impl Environment {
pub fn parse(raw: &str) -> Self {
match raw.trim().to_ascii_lowercase().as_str() {
"development" | "dev" => Self::Development,
_ => Self::Production,
}
}
pub fn exposes_infra_errors(self) -> bool {
matches!(self, Self::Development)
}
pub fn current() -> Self {
if let Some(env) = OVERRIDE.with(|c| c.get()) {
return env;
}
INSTALLED.get().copied().unwrap_or(Self::Production)
}
}
thread_local! {
static OVERRIDE: std::cell::Cell<Option<Environment>> = const { std::cell::Cell::new(None) };
}
static INSTALLED: OnceLock<Environment> = OnceLock::new();
static SETTINGS: OnceLock<ApplicationConfig> = OnceLock::new();
pub(crate) fn install_environment(env: Environment) {
let _ = INSTALLED.set(env);
}
pub(crate) fn install(cfg: ApplicationConfig) {
let _ = SETTINGS.set(cfg);
}
#[cfg(test)]
pub(crate) fn with_environment<R>(env: Environment, f: impl FnOnce() -> R) -> R {
OVERRIDE.with(|c| {
let prev = c.replace(Some(env));
let out = f();
c.set(prev);
out
})
}
#[derive(Debug, Clone)]
pub struct DatabasePoolConfig {
pub max_connections: u32,
pub acquire_timeout_ms: u32,
}
impl Default for DatabasePoolConfig {
fn default() -> Self {
Self {
max_connections: 32,
acquire_timeout_ms: 5000,
}
}
}
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub acquire_timeout_ms: u32,
}
#[derive(Debug, Clone)]
pub struct LogConfig {
pub level: String,
pub file: Option<String>,
pub stdout: bool,
pub json: bool,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: "info".into(),
file: None,
stdout: true,
json: false,
}
}
}
#[derive(Debug, Clone)]
pub struct HttpClientConfig {
pub timeout_ms: u32,
pub connect_timeout_ms: u32,
pub pool_max_idle_per_host: u32,
pub pool_idle_timeout_secs: Option<u32>,
pub user_agent: String,
pub max_in_flight: u32,
}
impl Default for HttpClientConfig {
fn default() -> Self {
Self {
timeout_ms: 30_000,
connect_timeout_ms: 10_000,
pool_max_idle_per_host: 32,
pool_idle_timeout_secs: None,
user_agent: "noema-actix-webapi".into(),
max_in_flight: 64,
}
}
}
#[derive(Debug, Clone)]
pub struct CorsConfig {
pub origins: Vec<String>,
pub methods: Vec<String>,
pub headers: Vec<String>,
pub credentials: bool,
pub max_age: u32,
}
impl Default for CorsConfig {
fn default() -> Self {
Self {
origins: Vec::new(),
methods: vec![
"GET".into(),
"POST".into(),
"PUT".into(),
"PATCH".into(),
"DELETE".into(),
"OPTIONS".into(),
],
headers: vec![
"Authorization".into(),
"Content-Type".into(),
"Accept".into(),
"Idempotency-Key".into(),
],
credentials: false,
max_age: 3600,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ApplicationConfig {
pub http: HttpConfig,
pub log: LogConfig,
pub cors: CorsConfig,
pub http_client: HttpClientConfig,
pub database: DatabasePoolConfig,
}
impl Injectable<Container> for ApplicationConfig {
fn inject(_: &Container) -> Self {
SETTINGS
.get()
.expect("ApplicationConfig not installed; call Application::start")
.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cors_default_allows_no_origins() {
assert!(CorsConfig::default().origins.is_empty());
}
}