use super::{ConfigError, ConfigManager};
use crate::{CoreError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConfigValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<ConfigValue>),
Object(HashMap<String, ConfigValue>),
Null,
}
pub struct ConfigManagerImpl {
data: RwLock<HashMap<String, serde_json::Value>>,
defaults: HashMap<String, serde_json::Value>,
}
impl ConfigManagerImpl {
pub fn new() -> Self {
Self {
data: RwLock::new(HashMap::new()),
defaults: HashMap::new(),
}
}
pub fn with_defaults(defaults: HashMap<String, serde_json::Value>) -> Self {
Self {
data: RwLock::new(defaults.clone()),
defaults,
}
}
fn replace_env_vars(&self, input: &str) -> String {
let mut result = String::new();
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '$' && chars.peek() == Some(&'{') {
chars.next();
let mut var_name = String::new();
while let Some(&c) = chars.peek() {
if c == '}' {
chars.next();
break;
}
var_name.push(chars.next().unwrap());
}
if let Ok(var_value) = std::env::var(&var_name) {
result.push_str(&var_value);
} else {
result.push_str(&format!("${{{}}}", var_name));
}
} else {
result.push(c);
}
}
result
}
fn replace_env_vars_in_json(&self, value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::String(s) => {
let replaced = self.replace_env_vars(&s);
serde_json::Value::String(replaced)
},
serde_json::Value::Array(arr) => {
let replaced = arr
.into_iter()
.map(|v| self.replace_env_vars_in_json(v))
.collect();
serde_json::Value::Array(replaced)
},
serde_json::Value::Object(obj) => {
let replaced = obj
.into_iter()
.map(|(k, v)| (k, self.replace_env_vars_in_json(v)))
.collect();
serde_json::Value::Object(replaced)
},
v => v,
}
}
}
impl Default for ConfigManagerImpl {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl ConfigManager for ConfigManagerImpl {
async fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Result<T> {
let data = self.data.read().await;
if let Some(value) = data.get(key) {
return serde_json::from_value(value.clone())
.map_err(|e| CoreError::JsonError(e.to_string()));
}
if let Some(value) = self.defaults.get(key) {
return serde_json::from_value(value.clone())
.map_err(|e| CoreError::JsonError(e.to_string()));
}
Err(CoreError::ConfigError(ConfigError::KeyNotFound(
key.to_string(),
)))
}
async fn set<T: serde::Serialize + Send>(&self, key: &str, value: T) -> Result<()> {
let mut data = self.data.write().await;
let json_value =
serde_json::to_value(value).map_err(|e| CoreError::JsonError(e.to_string()))?;
data.insert(key.to_string(), json_value);
Ok(())
}
async fn delete(&self, key: &str) -> Result<bool> {
let mut data = self.data.write().await;
Ok(data.remove(key).is_some())
}
async fn exists(&self, key: &str) -> Result<bool> {
let data = self.data.read().await;
Ok(data.contains_key(key) || self.defaults.contains_key(key))
}
async fn list_keys(&self) -> Result<Vec<String>> {
let data = self.data.read().await;
let mut keys: Vec<String> = data.keys().cloned().collect();
for key in self.defaults.keys() {
if !keys.contains(key) {
keys.push(key.clone());
}
}
keys.sort();
Ok(keys)
}
async fn load_from_file(&self, path: &Path) -> Result<()> {
if !path.exists() {
return Err(CoreError::ConfigError(ConfigError::FileNotFound(
path.to_string_lossy().to_string(),
)));
}
let content = fs::read_to_string(path).map_err(|e| CoreError::IoError(e.to_string()))?;
let content_with_env = self.replace_env_vars(&content);
let mut config: HashMap<String, serde_json::Value> =
serde_json::from_str(&content_with_env)
.map_err(|e| CoreError::ConfigError(ConfigError::ParseFailed(e.to_string())))?;
for (_, value) in config.iter_mut() {
*value = self.replace_env_vars_in_json(value.clone());
}
let mut data = self.data.write().await;
data.extend(config);
Ok(())
}
async fn save_to_file(&self, path: &Path) -> Result<()> {
let data = self.data.read().await;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| CoreError::IoError(e.to_string()))?;
}
let mut file = File::create(path).map_err(|e| CoreError::IoError(e.to_string()))?;
let json_content = serde_json::to_string_pretty(&*data)
.map_err(|e| CoreError::JsonError(e.to_string()))?;
file.write_all(json_content.as_bytes())
.map_err(|e| CoreError::IoError(e.to_string()))?;
Ok(())
}
async fn reload(&self) -> Result<()> {
let mut data = self.data.write().await;
*data = self.defaults.clone();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
use tempfile::tempdir;
#[tokio::test]
async fn test_config_manager_new() {
let config_manager = ConfigManagerImpl::new();
assert_eq!(config_manager.list_keys().await.unwrap().len(), 0);
}
#[tokio::test]
async fn test_config_manager_with_defaults() {
let mut defaults = HashMap::new();
defaults.insert("key1".to_string(), json!("value1"));
defaults.insert("key2".to_string(), json!(42));
let config_manager = ConfigManagerImpl::with_defaults(defaults);
let keys = config_manager.list_keys().await.unwrap();
assert_eq!(keys.len(), 2);
assert!(keys.contains(&"key1".to_string()));
assert!(keys.contains(&"key2".to_string()));
let value1: String = config_manager.get("key1").await.unwrap();
let value2: i64 = config_manager.get("key2").await.unwrap();
assert_eq!(value1, "value1");
assert_eq!(value2, 42);
}
#[tokio::test]
async fn test_config_manager_set_get() {
let config_manager = ConfigManagerImpl::new();
config_manager
.set("string_key", "test_value")
.await
.unwrap();
config_manager.set("int_key", 123).await.unwrap();
config_manager.set("bool_key", true).await.unwrap();
let string_value: String = config_manager.get("string_key").await.unwrap();
let int_value: i64 = config_manager.get("int_key").await.unwrap();
let bool_value: bool = config_manager.get("bool_key").await.unwrap();
assert_eq!(string_value, "test_value");
assert_eq!(int_value, 123);
assert_eq!(bool_value, true);
}
#[tokio::test]
async fn test_config_manager_delete() {
let config_manager = ConfigManagerImpl::new();
config_manager.set("test_key", "test_value").await.unwrap();
let exists_before = config_manager.exists("test_key").await.unwrap();
assert!(exists_before);
let deleted = config_manager.delete("test_key").await.unwrap();
assert!(deleted);
let exists_after = config_manager.exists("test_key").await.unwrap();
assert!(!exists_after);
let deleted_again = config_manager.delete("test_key").await.unwrap();
assert!(!deleted_again);
}
#[tokio::test]
async fn test_config_manager_load_save_file() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("config.json");
let config_manager = ConfigManagerImpl::new();
config_manager.set("test_key", "test_value").await.unwrap();
config_manager.set("another_key", 42).await.unwrap();
config_manager.save_to_file(&config_path).await.unwrap();
let config_manager2 = ConfigManagerImpl::new();
config_manager2.load_from_file(&config_path).await.unwrap();
let value1: String = config_manager2.get("test_key").await.unwrap();
let value2: i64 = config_manager2.get("another_key").await.unwrap();
assert_eq!(value1, "test_value");
assert_eq!(value2, 42);
}
#[tokio::test]
async fn test_config_manager_reload() {
let mut defaults = HashMap::new();
defaults.insert("default_key".to_string(), json!("default_value"));
let config_manager = ConfigManagerImpl::with_defaults(defaults);
config_manager.set("test_key", "test_value").await.unwrap();
config_manager
.set("default_key", "modified_value")
.await
.unwrap();
config_manager.reload().await.unwrap();
let default_value: String = config_manager.get("default_key").await.unwrap();
assert_eq!(default_value, "default_value");
let exists = config_manager.exists("test_key").await.unwrap();
assert!(!exists);
}
#[tokio::test]
async fn test_config_manager_env_vars() {
std::env::set_var("TEST_ENV_VAR", "env_value");
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("config.json");
let config_content = r#"{
"env_key": "${TEST_ENV_VAR}",
"regular_key": "regular_value"
}"#;
std::fs::write(&config_path, config_content).unwrap();
let config_manager = ConfigManagerImpl::new();
config_manager.load_from_file(&config_path).await.unwrap();
let env_value: String = config_manager.get("env_key").await.unwrap();
let regular_value: String = config_manager.get("regular_key").await.unwrap();
assert_eq!(env_value, "env_value");
assert_eq!(regular_value, "regular_value");
std::env::remove_var("TEST_ENV_VAR");
}
}