pub mod builder;
pub mod models;
pub use models::*;
use crate::utils::error::{GatewayError, Result};
use std::path::Path;
use tracing::{debug, info};
#[derive(Debug, Clone, Default)]
pub struct Config {
pub gateway: GatewayConfig,
}
#[allow(dead_code)]
impl Config {
pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
info!("Loading configuration from: {:?}", path);
let content = tokio::fs::read_to_string(path)
.await
.map_err(|e| GatewayError::Config(format!("Failed to read config file: {}", e)))?;
let gateway: GatewayConfig = serde_yaml::from_str(&content)
.map_err(|e| GatewayError::Config(format!("Failed to parse config: {}", e)))?;
let config = Self { gateway };
config.validate()?;
debug!("Configuration loaded successfully");
Ok(config)
}
pub fn from_env() -> Result<Self> {
info!("Loading configuration from environment variables");
let gateway = GatewayConfig::from_env()?;
let config = Self { gateway };
config.validate()?;
Ok(config)
}
pub fn server(&self) -> &ServerConfig {
&self.gateway.server
}
pub fn providers(&self) -> &[ProviderConfig] {
&self.gateway.providers
}
pub fn router(&self) -> &RouterConfig {
&self.gateway.router
}
pub fn storage(&self) -> &StorageConfig {
&self.gateway.storage
}
pub fn auth(&self) -> &AuthConfig {
&self.gateway.auth
}
pub fn monitoring(&self) -> &MonitoringConfig {
&self.gateway.monitoring
}
pub fn validate(&self) -> Result<()> {
debug!("Validating configuration");
self.gateway
.server
.validate()
.map_err(|e| GatewayError::Config(format!("Server config error: {}", e)))?;
self.gateway
.auth
.validate()
.map_err(|e| GatewayError::Config(format!("Auth config error: {}", e)))?;
self.gateway
.server
.cors
.validate()
.map_err(|e| GatewayError::Config(format!("CORS config error: {}", e)))?;
crate::config::models::auth::warn_insecure_config(&self.gateway.auth);
debug!("Configuration validation completed");
Ok(())
}
pub fn merge(mut self, other: Self) -> Self {
self.gateway = self.gateway.merge(other.gateway);
self
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(&self.gateway)
.map_err(|e| GatewayError::Config(format!("Failed to serialize config to JSON: {}", e)))
}
pub fn to_yaml(&self) -> Result<String> {
serde_yaml::to_string(&self.gateway)
.map_err(|e| GatewayError::Config(format!("Failed to serialize config to YAML: {}", e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_config_from_file() {
let config_content = r#"
server:
host: "127.0.0.1"
port: 8080
workers: 4
providers:
- name: "openai"
provider_type: "openai"
api_key: "test-key"
api_base: "https://api.openai.com/v1"
router:
strategy:
type: "round_robin"
circuit_breaker:
failure_threshold: 5
recovery_timeout: 30
storage:
database:
url: "postgresql://localhost/gateway"
redis:
url: "redis://localhost:6379"
auth:
jwt_secret: "test-secret-that-is-at-least-32-characters-long-for-security"
api_key_header: "Authorization"
monitoring:
metrics:
enabled: true
port: 9090
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_content.as_bytes()).unwrap();
let config = Config::from_file(temp_file.path()).await.unwrap();
assert_eq!(config.server().host, "127.0.0.1");
assert_eq!(config.server().port, 8080);
assert_eq!(config.providers().len(), 1);
assert_eq!(config.providers()[0].name, "openai");
}
#[test]
fn test_default_config() {
let config = Config::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_serialization() {
let config = Config::default();
let json = config.to_json().unwrap();
assert!(!json.is_empty());
let yaml = config.to_yaml().unwrap();
assert!(!yaml.is_empty());
}
}