use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagingModuleConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub module: ModuleConfig,
pub features: FeaturesConfig,
pub logging: LoggingConfig,
pub tracing: TracingConfig,
pub metrics: MetricsConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub grpc_port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub min_connections: u32,
pub connect_timeout: u64,
pub idle_timeout: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleConfig {
pub name: String,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeaturesConfig {
pub soft_delete: bool,
pub state_machines: bool,
pub flows: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub level: String,
pub format: String,
pub file: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracingConfig {
pub enabled: bool,
pub exporter: Option<String>,
pub endpoint: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsConfig {
pub enabled: bool,
pub endpoint: Option<String>,
pub port: Option<u16>,
}
impl MessagingModuleConfig {
pub fn load(config_dir: &Path, environment: &str) -> anyhow::Result<Self> {
let base_path = config_dir.join("application.yml");
let env_path = config_dir.join(format!("application-{}.yml", environment));
let base_content = std::fs::read_to_string(&base_path)
.map_err(|e| anyhow::anyhow!("Failed to read base config: {}", e))?;
let mut config: serde_yaml::Value = serde_yaml::from_str(&base_content)?;
if env_path.exists() {
let env_content = std::fs::read_to_string(&env_path)?;
let env_config: serde_yaml::Value = serde_yaml::from_str(&env_content)?;
merge_yaml(&mut config, env_config);
}
let config_str = serde_yaml::to_string(&config)?;
let config_str = expand_env_vars(&config_str);
Ok(serde_yaml::from_str(&config_str)?)
}
pub fn load_default() -> anyhow::Result<Self> {
let config_dir = std::env::current_dir()?.join("config");
let environment = std::env::var("APP_ENV").unwrap_or_else(|_| "dev".to_string());
Self::load(&config_dir, &environment)
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: "0.0.0.0".to_string(),
port: 3000,
grpc_port: 50051,
}
}
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
url: "postgresql://root:password@localhost:5432/messaging".to_string(),
max_connections: 20,
min_connections: 5,
connect_timeout: 30,
idle_timeout: 600,
}
}
}
fn merge_yaml(base: &mut serde_yaml::Value, overlay: serde_yaml::Value) {
if let (serde_yaml::Value::Mapping(base_map), serde_yaml::Value::Mapping(overlay_map)) =
(base, overlay)
{
for (key, value) in overlay_map {
if let Some(base_value) = base_map.get_mut(&key) {
if value.is_mapping() && base_value.is_mapping() {
merge_yaml(base_value, value);
} else {
*base_value = value;
}
} else {
base_map.insert(key, value);
}
}
}
}
fn expand_env_vars(content: &str) -> String {
let mut result = content.to_string();
let re = regex::Regex::new(r"\$\{([A-Z_][A-Z0-9_]*)(?::([^}]*))?\}").unwrap();
for cap in re.captures_iter(content) {
let full_match = cap.get(0).unwrap().as_str();
let var_name = cap.get(1).unwrap().as_str();
let default_value = cap.get(2).map(|m| m.as_str()).unwrap_or("");
let value = std::env::var(var_name).unwrap_or_else(|_| default_value.to_string());
result = result.replace(full_match, &value);
}
result
}