backbone-mail 0.2.32

Odoo mail core port — message/notification/followers/activity/alias/sms queue (schema: messaging)
//! Configuration types for Messaging module
//!
//! Generated by metaphor-schema. Do not edit manually.

use serde::{Deserialize, Serialize};
use std::path::Path;

/// Main configuration for Messaging module
#[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 {
    /// Load configuration from YAML files
    ///
    /// Loads configuration with environment-specific overrides:
    /// 1. application.yml (base)
    /// 2. application-{env}.yml (environment override)
    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));

        // Load base configuration
        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)?;

        // Load environment overrides if exists
        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);
        }

        // Apply environment variable overrides
        let config_str = serde_yaml::to_string(&config)?;
        let config_str = expand_env_vars(&config_str);

        Ok(serde_yaml::from_str(&config_str)?)
    }

    /// Load from default location with auto-detected environment
    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,
        }
    }
}

/// Merge two YAML values, with the second taking precedence
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);
            }
        }
    }
}

/// Expand environment variable references in config
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
}