use config::{Config, ConfigError, Environment, File};
use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub providers: ProvidersConfig,
pub security: SecurityConfig,
pub logging: LoggingConfig,
pub rate_limit: RateLimitConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub timeout_seconds: u64,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ProvidersConfig {
pub plivo: Option<PlivoConfig>,
pub twilio: Option<TwilioConfig>,
pub aws_sns: Option<AwsSnsConfig>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PlivoConfig {
pub auth_id: String,
pub auth_token: String,
pub verify_signatures: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TwilioConfig {
pub account_sid: String,
pub auth_token: String,
pub verify_signatures: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AwsSnsConfig {
pub access_key_id: String,
pub secret_access_key: String,
pub region: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SecurityConfig {
pub verify_signatures: bool,
pub max_body_size: usize,
pub request_timeout: u64,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct LoggingConfig {
pub level: String,
pub format: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RateLimitConfig {
pub enabled: bool,
pub requests_per_minute: u32,
pub burst_size: u32,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: "0.0.0.0".to_string(),
port: 3000,
timeout_seconds: 30,
}
}
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
verify_signatures: true,
max_body_size: 1024 * 1024, request_timeout: 30,
}
}
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
format: "json".to_string(),
}
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
enabled: true,
requests_per_minute: 100,
burst_size: 10,
}
}
}
impl AppConfig {
pub fn load() -> Result<Self, ConfigError> {
let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
let s = Config::builder()
.add_source(Config::try_from(&AppConfig::default())?)
.add_source(File::with_name("config/default").required(false))
.add_source(File::with_name(&format!("config/{}", run_mode)).required(false))
.add_source(File::with_name("config/local").required(false))
.add_source(Environment::with_prefix("SMSKIT").separator("__"))
.build()?;
s.try_deserialize()
}
}
impl Default for AppConfig {
fn default() -> Self {
Self {
server: ServerConfig::default(),
providers: ProvidersConfig {
plivo: None,
twilio: None,
aws_sns: None,
},
security: SecurityConfig::default(),
logging: LoggingConfig::default(),
rate_limit: RateLimitConfig::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_server_config() {
let cfg = ServerConfig::default();
assert_eq!(cfg.host, "0.0.0.0");
assert_eq!(cfg.port, 3000);
assert_eq!(cfg.timeout_seconds, 30);
}
#[test]
fn default_security_config() {
let cfg = SecurityConfig::default();
assert!(cfg.verify_signatures);
assert_eq!(cfg.max_body_size, 1024 * 1024);
assert_eq!(cfg.request_timeout, 30);
}
#[test]
fn default_logging_config() {
let cfg = LoggingConfig::default();
assert_eq!(cfg.level, "info");
assert_eq!(cfg.format, "json");
}
#[test]
fn default_rate_limit_config() {
let cfg = RateLimitConfig::default();
assert!(cfg.enabled);
assert_eq!(cfg.requests_per_minute, 100);
assert_eq!(cfg.burst_size, 10);
}
#[test]
fn default_app_config_has_no_providers() {
let cfg = AppConfig::default();
assert!(cfg.providers.plivo.is_none());
assert!(cfg.providers.twilio.is_none());
assert!(cfg.providers.aws_sns.is_none());
}
#[test]
fn app_config_serde_roundtrip() {
let cfg = AppConfig::default();
let json = serde_json::to_string(&cfg).unwrap();
let deser: AppConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deser.server.port, 3000);
assert_eq!(deser.security.max_body_size, 1024 * 1024);
}
}