use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TlsRuntimeConfig {
pub enabled: bool,
pub cert_file: String,
pub key_file: String,
pub min_version: String,
}
impl Default for TlsRuntimeConfig {
fn default() -> Self {
Self {
enabled: false,
cert_file: String::new(),
key_file: String::new(),
min_version: "1.2".to_string(),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CorsRuntimeConfig {
pub origins: Vec<String>,
pub credentials: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ServerRuntimeConfig {
pub host: String,
pub port: u16,
pub request_timeout_ms: u64,
pub keep_alive_secs: u64,
pub cors: CorsRuntimeConfig,
pub tls: TlsRuntimeConfig,
}
impl Default for ServerRuntimeConfig {
fn default() -> Self {
Self {
host: "0.0.0.0".to_string(),
port: 8080,
request_timeout_ms: 30_000,
keep_alive_secs: 75,
cors: CorsRuntimeConfig::default(),
tls: TlsRuntimeConfig::default(),
}
}
}
impl ServerRuntimeConfig {
pub fn validate(&self) -> Result<()> {
if self.port == 0 {
anyhow::bail!("[server] port must be non-zero");
}
if self.tls.enabled {
if self.tls.cert_file.is_empty() {
anyhow::bail!("[server.tls] cert_file is required when tls.enabled = true");
}
if self.tls.key_file.is_empty() {
anyhow::bail!("[server.tls] key_file is required when tls.enabled = true");
}
if self.tls.min_version != "1.2" && self.tls.min_version != "1.3" {
anyhow::bail!(
"[server.tls] min_version must be \"1.2\" or \"1.3\", got \"{}\"",
self.tls.min_version
);
}
}
Ok(())
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DatabaseRuntimeConfig {
pub url: Option<String>,
pub pool_min: usize,
pub pool_max: usize,
pub connect_timeout_ms: u64,
pub idle_timeout_ms: u64,
pub ssl_mode: String,
}
impl Default for DatabaseRuntimeConfig {
fn default() -> Self {
Self {
url: None,
pool_min: 2,
pool_max: 20,
connect_timeout_ms: 5_000,
idle_timeout_ms: 600_000,
ssl_mode: "prefer".to_string(),
}
}
}
impl DatabaseRuntimeConfig {
pub fn validate(&self) -> Result<()> {
const VALID_SSL: &[&str] = &["disable", "allow", "prefer", "require"];
if self.pool_min > self.pool_max {
anyhow::bail!(
"[database] pool_min ({}) must be <= pool_max ({})",
self.pool_min,
self.pool_max
);
}
if !VALID_SSL.contains(&self.ssl_mode.as_str()) {
anyhow::bail!(
"[database] ssl_mode must be one of {:?}, got \"{}\"",
VALID_SSL,
self.ssl_mode
);
}
Ok(())
}
}