use std::path::PathBuf; use anyhow::{Context, Result}; use sqlx::postgres::PgSslMode; #[derive(Debug, Clone)] pub struct DatabaseConfig { pub url: String, pub ssl_mode: Option, pub ssl_root_cert: Option, pub enforce_strict_tls: bool, } /// Resolve database URL from environment. /// Priority: `SECRETS_DATABASE_URL` env var → error. pub fn resolve_db_url(override_url: &str) -> Result { if !override_url.is_empty() { return Ok(override_url.to_string()); } if let Ok(url) = std::env::var("SECRETS_DATABASE_URL") && !url.is_empty() { return Ok(url); } anyhow::bail!( "Database not configured. Set the SECRETS_DATABASE_URL environment variable.\n\ Example: SECRETS_DATABASE_URL=postgres://user:pass@host:port/dbname" ) } fn env_var_non_empty(name: &str) -> Option { std::env::var(name) .ok() .filter(|value| !value.trim().is_empty()) } fn parse_ssl_mode_from_env() -> Result> { let Some(mode) = env_var_non_empty("SECRETS_DATABASE_SSL_MODE") else { return Ok(None); }; let parsed = mode.parse::().with_context(|| { format!( "Invalid SECRETS_DATABASE_SSL_MODE='{mode}'. Use one of: disable, allow, prefer, require, verify-ca, verify-full." ) })?; Ok(Some(parsed)) } fn resolve_ssl_root_cert_from_env() -> Result> { let Some(path) = env_var_non_empty("SECRETS_DATABASE_SSL_ROOT_CERT") else { return Ok(None); }; let path = PathBuf::from(path); if !path.exists() { anyhow::bail!( "SECRETS_DATABASE_SSL_ROOT_CERT points to a missing file: {}", path.display() ); } Ok(Some(path)) } fn is_production_env() -> bool { matches!( env_var_non_empty("SECRETS_ENV") .as_deref() .map(|value| value.to_ascii_lowercase()), Some(value) if value == "prod" || value == "production" ) } pub fn resolve_db_config(override_url: &str) -> Result { Ok(DatabaseConfig { url: resolve_db_url(override_url)?, ssl_mode: parse_ssl_mode_from_env()?, ssl_root_cert: resolve_ssl_root_cert_from_env()?, enforce_strict_tls: is_production_env(), }) }