use crate::utils::error::gateway_error::{GatewayError, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::sync::LazyLock;
#[derive(Debug)]
pub struct OptimizedConfigManager {
cache: Arc<RwLock<HashMap<String, Arc<ConfigValue>>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConfigValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<ConfigValue>),
Object(HashMap<String, ConfigValue>),
}
impl ConfigValue {
pub fn as_string(&self) -> Option<&str> {
match self {
ConfigValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
ConfigValue::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
ConfigValue::Float(f) => Some(*f),
ConfigValue::Integer(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
ConfigValue::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn get_nested(&self, path: &str) -> Option<&ConfigValue> {
let parts: Vec<&str> = path.split('.').collect();
let mut current = self;
for part in parts {
match current {
ConfigValue::Object(map) => {
current = map.get(part)?;
}
ConfigValue::Array(arr) => {
let index: usize = part.parse().ok()?;
current = arr.get(index)?;
}
_ => return None,
}
}
Some(current)
}
}
impl OptimizedConfigManager {
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn load_config<T>(&self, file_path: &str) -> Result<Arc<T>>
where
T: for<'de> Deserialize<'de> + Serialize + Send + Sync + 'static,
{
{
let cache = self.cache.read();
if let Some(cached) = cache.get(file_path)
&& let Ok(config) = self.try_downcast_config::<T>(cached.clone())
{
return Ok(config);
}
}
let config = self.load_from_file::<T>(file_path).await?;
let config_arc = Arc::new(config);
{
let mut cache = self.cache.write();
let config_value = self.serialize_to_config_value(&*config_arc)?;
cache.insert(file_path.to_string(), Arc::new(config_value));
}
Ok(config_arc)
}
async fn load_from_file<T>(&self, file_path: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
let content = tokio::fs::read_to_string(file_path).await.map_err(|e| {
GatewayError::Config(format!("Failed to read config file {}: {}", file_path, e))
})?;
let extension = Path::new(file_path)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("yaml");
match extension {
"yaml" | "yml" => serde_yml::from_str(&content)
.map_err(|e| GatewayError::Config(format!("Failed to parse YAML config: {}", e))),
"json" => serde_json::from_str(&content)
.map_err(|e| GatewayError::Config(format!("Failed to parse JSON config: {}", e))),
"toml" => {
Err(GatewayError::Config("TOML support not enabled".to_string()))
}
_ => Err(GatewayError::Config(format!(
"Unsupported config format: {}",
extension
))),
}
}
pub fn clear_cache(&self, file_path: &str) {
let mut cache = self.cache.write();
cache.remove(file_path);
}
pub fn clear_all_cache(&self) {
let mut cache = self.cache.write();
cache.clear();
}
pub fn get_cache_stats(&self) -> HashMap<String, usize> {
let cache = self.cache.read();
cache
.iter()
.map(|(k, v)| (k.clone(), std::mem::size_of_val(&**v)))
.collect()
}
fn try_downcast_config<T>(&self, _config_value: Arc<ConfigValue>) -> Result<Arc<T>>
where
T: for<'de> Deserialize<'de> + Send + Sync + 'static,
{
Err(GatewayError::Config(
"Type downcast not implemented".to_string(),
))
}
fn serialize_to_config_value<T>(&self, _config: &T) -> Result<ConfigValue>
where
T: Serialize,
{
Ok(ConfigValue::Object(HashMap::new()))
}
}
impl Default for OptimizedConfigManager {
fn default() -> Self {
Self::new()
}
}
pub static GLOBAL_CONFIG_MANAGER: LazyLock<OptimizedConfigManager> =
LazyLock::new(OptimizedConfigManager::new);
pub async fn load_config<T>(file_path: &str) -> Result<Arc<T>>
where
T: for<'de> Deserialize<'de> + Serialize + Send + Sync + 'static,
{
GLOBAL_CONFIG_MANAGER.load_config(file_path).await
}
pub struct ConfigPresets;
impl ConfigPresets {
pub fn development() -> HashMap<String, ConfigValue> {
let mut config = HashMap::new();
config.insert(
"log_level".to_string(),
ConfigValue::String("debug".to_string()),
);
config.insert("cache_size".to_string(), ConfigValue::Integer(1000));
config.insert("enable_metrics".to_string(), ConfigValue::Boolean(true));
config
}
pub fn production() -> HashMap<String, ConfigValue> {
let mut config = HashMap::new();
config.insert(
"log_level".to_string(),
ConfigValue::String("info".to_string()),
);
config.insert("cache_size".to_string(), ConfigValue::Integer(10000));
config.insert("enable_metrics".to_string(), ConfigValue::Boolean(true));
config
}
pub fn testing() -> HashMap<String, ConfigValue> {
let mut config = HashMap::new();
config.insert(
"log_level".to_string(),
ConfigValue::String("warn".to_string()),
);
config.insert("cache_size".to_string(), ConfigValue::Integer(100));
config.insert("enable_metrics".to_string(), ConfigValue::Boolean(false));
config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_value_navigation() {
let mut root = HashMap::new();
let mut database = HashMap::new();
database.insert(
"host".to_string(),
ConfigValue::String("localhost".to_string()),
);
database.insert("port".to_string(), ConfigValue::Integer(5432));
root.insert("database".to_string(), ConfigValue::Object(database));
let config = ConfigValue::Object(root);
assert_eq!(
config
.get_nested("database.host")
.and_then(|v| v.as_string()),
Some("localhost")
);
assert_eq!(
config.get_nested("database.port").and_then(|v| v.as_i64()),
Some(5432)
);
}
#[test]
fn test_config_presets() {
let dev_config = ConfigPresets::development();
assert_eq!(
dev_config.get("log_level").and_then(|v| v.as_string()),
Some("debug")
);
let prod_config = ConfigPresets::production();
assert_eq!(
prod_config.get("cache_size").and_then(|v| v.as_i64()),
Some(10000)
);
}
}