pg-api 0.2.0

A high-performance PostgreSQL REST API driver with rate limiting, connection pooling, and observability
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use std::env;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    pub host: IpAddr,
    pub port: u16,
    pub log_level: String,
    pub cors: CorsConfig,
    pub limits: LimitsConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorsConfig {
    pub enabled: bool,
    pub origins: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LimitsConfig {
    pub max_request_size_mb: usize,
    pub request_timeout_seconds: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Número máximo de conexões no pool (padrão: 30, máximo: 750)
    pub max_connections: usize,
    /// Número mínimo de conexões mantidas abertas (padrão: 5)
    pub min_idle: usize,
    /// Tempo máximo de espera na fila em milissegundos (padrão: 30000)
    pub queue_timeout_ms: u64,
    /// Tempo de vida máximo de uma conexão em minutos (padrão: 30)
    pub max_lifetime_minutes: u64,
    /// Habilitar fila global quando pool esgotado (padrão: true)
    pub enable_queue: bool,
    /// Número de threads workers para processar requisições (padrão: 10)
    pub worker_threads: usize,
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            max_connections: 30,
            min_idle: 5,
            queue_timeout_ms: 30000,
            max_lifetime_minutes: 30,
            enable_queue: true,
            worker_threads: 10,
        }
    }
}

impl PoolConfig {
    /// Valida a configuração do pool
    pub fn validate(&self) -> Result<(), String> {
        if self.max_connections == 0 {
            return Err("max_connections deve ser maior que 0".to_string());
        }
        if self.max_connections > 750 {
            return Err("max_connections não pode exceder 750".to_string());
        }
        if self.min_idle > self.max_connections {
            return Err("min_idle não pode ser maior que max_connections".to_string());
        }
        if self.worker_threads == 0 {
            return Err("worker_threads deve ser maior que 0".to_string());
        }
        Ok(())
    }
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".parse().unwrap(),
            port: 8560,
            log_level: "info".to_string(),
            cors: CorsConfig {
                enabled: true,
                origins: vec!["*".to_string()],
            },
            limits: LimitsConfig {
                max_request_size_mb: 10,
                request_timeout_seconds: 60,
            },
        }
    }
}

pub async fn load_server_config() -> Result<ServerConfig> {
    let config_dir = env::var("CONFIG_DIR").unwrap_or_else(|_| "config".to_string());
    let config_path = PathBuf::from(config_dir).join("server.json");
    
    if let Ok(content) = tokio::fs::read_to_string(&config_path).await
        && let Ok(config) = serde_json::from_str::<ServerConfig>(&content) {
            return Ok(config);
        }
    
    Ok(ServerConfig::default())
}

pub async fn load_pool_config() -> Result<PoolConfig> {
    let config_dir = env::var("CONFIG_DIR").unwrap_or_else(|_| "config".to_string());
    let config_path = PathBuf::from(config_dir).join("pool.json");
    
    tracing::info!("Loading pool configuration from: {}", config_path.display());
    
    if let Ok(content) = tokio::fs::read_to_string(&config_path).await {
        match serde_json::from_str::<PoolConfig>(&content) {
            Ok(config) => {
                if let Err(e) = config.validate() {
                    tracing::error!("Invalid pool configuration: {}", e);
                    tracing::warn!("Using default pool configuration");
                    return Ok(PoolConfig::default());
                }
                tracing::info!(
                    "Pool configuration loaded: max_connections={}, min_idle={}, enable_queue={}",
                    config.max_connections,
                    config.min_idle,
                    config.enable_queue
                );
                return Ok(config);
            }
            Err(e) => {
                tracing::error!("Failed to parse pool.json: {}", e);
                tracing::warn!("Using default pool configuration");
            }
        }
    } else {
        tracing::warn!("pool.json not found, using default configuration");
    }
    
    Ok(PoolConfig::default())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_server_config_default() {
        let config = ServerConfig::default();
        assert_eq!(config.host.to_string(), "127.0.0.1");
        assert_eq!(config.port, 8560);
        assert_eq!(config.log_level, "info");
        assert!(config.cors.enabled);
        assert_eq!(config.limits.max_request_size_mb, 10);
    }

    #[test]
    fn test_pool_config_default() {
        let config = PoolConfig::default();
        assert_eq!(config.max_connections, 30);
        assert_eq!(config.min_idle, 5);
        assert_eq!(config.queue_timeout_ms, 30000);
        assert_eq!(config.max_lifetime_minutes, 30);
        assert!(config.enable_queue);
        assert_eq!(config.worker_threads, 10);
    }

    #[test]
    fn test_pool_config_validate_valid() {
        let config = PoolConfig::default();
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_pool_config_validate_zero_max_connections() {
        let config = PoolConfig {
            max_connections: 0,
            ..Default::default()
        };
        let result = config.validate();
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("max_connections"));
    }

    #[test]
    fn test_pool_config_validate_too_many_connections() {
        let config = PoolConfig {
            max_connections: 1000,
            ..Default::default()
        };
        let result = config.validate();
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("750"));
    }

    #[test]
    fn test_pool_config_validate_min_idle_greater_than_max() {
        let config = PoolConfig {
            max_connections: 10,
            min_idle: 20,
            ..Default::default()
        };
        let result = config.validate();
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("min_idle"));
    }

    #[test]
    fn test_pool_config_validate_zero_worker_threads() {
        let config = PoolConfig {
            worker_threads: 0,
            ..Default::default()
        };
        let result = config.validate();
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("worker_threads"));
    }

    #[test]
    fn test_pool_config_serialization() {
        let config = PoolConfig::default();
        let json = serde_json::to_string(&config).unwrap();
        assert!(json.contains("max_connections"));
        assert!(json.contains("30"));
    }

    #[test]
    fn test_pool_config_deserialization() {
        let json_str = r#"{
            "max_connections": 50,
            "min_idle": 10,
            "queue_timeout_ms": 60000,
            "max_lifetime_minutes": 60,
            "enable_queue": false,
            "worker_threads": 20
        }"#;

        let config: PoolConfig = serde_json::from_str(json_str).unwrap();
        assert_eq!(config.max_connections, 50);
        assert_eq!(config.min_idle, 10);
        assert_eq!(config.queue_timeout_ms, 60000);
        assert_eq!(config.max_lifetime_minutes, 60);
        assert!(!config.enable_queue);
        assert_eq!(config.worker_threads, 20);
    }

    #[test]
    fn test_server_config_serialization() {
        let config = ServerConfig::default();
        let json = serde_json::to_string(&config).unwrap();
        assert!(json.contains("127.0.0.1"));
        assert!(json.contains("8560"));
        assert!(json.contains("info"));
    }

    #[test]
    fn test_cors_config_deserialization() {
        let json_str = r#"{
            "enabled": true,
            "origins": ["https://example.com", "https://app.example.com"]
        }"#;

        let config: CorsConfig = serde_json::from_str(json_str).unwrap();
        assert!(config.enabled);
        assert_eq!(config.origins.len(), 2);
        assert_eq!(config.origins[0], "https://example.com");
    }

    #[test]
    fn test_limits_config_deserialization() {
        let json_str = r#"{
            "max_request_size_mb": 50,
            "request_timeout_seconds": 120
        }"#;

        let config: LimitsConfig = serde_json::from_str(json_str).unwrap();
        assert_eq!(config.max_request_size_mb, 50);
        assert_eq!(config.request_timeout_seconds, 120);
    }
}