pub mod config_service;
pub mod env;
pub mod error;
pub mod loader;
pub mod validation;
pub use config_service::ConfigService;
pub use env::EnvLoader;
pub use error::{ConfigError, Result};
pub use loader::{ConfigLoader, FileFormat};
pub use validation::{ConfigValidator, Validate};
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct ConfigManager {
config: Arc<RwLock<HashMap<String, serde_json::Value>>>,
env_prefix: Option<String>,
}
impl ConfigManager {
pub fn new() -> Self {
Self {
config: Arc::new(RwLock::new(HashMap::new())),
env_prefix: None,
}
}
pub fn with_prefix(prefix: String) -> Self {
Self {
config: Arc::new(RwLock::new(HashMap::new())),
env_prefix: Some(prefix),
}
}
pub fn load_env(&self) -> Result<()> {
let loader = EnvLoader::new(self.env_prefix.clone());
let env_vars = loader.load()?;
let mut config = self.config.write().unwrap();
for (key, value) in env_vars {
config.insert(key, serde_json::Value::String(value));
}
Ok(())
}
pub fn load_dotenv(&self, path: Option<&str>) -> Result<()> {
if let Some(path) = path {
dotenvy::from_path(path).map_err(|e| ConfigError::LoadError(e.to_string()))?;
} else {
dotenvy::dotenv().ok(); }
self.load_env()
}
pub fn load_file(&self, path: &str, format: FileFormat) -> Result<()> {
let loader = ConfigLoader::new(format);
let data = loader.load_file(path)?;
let mut config = self.config.write().unwrap();
if let serde_json::Value::Object(map) = data {
for (key, value) in map {
config.insert(key, value);
}
}
Ok(())
}
pub fn set<T: serde::Serialize>(&self, key: &str, value: T) -> Result<()> {
let json_value = serde_json::to_value(value)
.map_err(|e| ConfigError::SerializationError(e.to_string()))?;
let mut config = self.config.write().unwrap();
config.insert(key.to_string(), json_value);
Ok(())
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<T> {
let config = self.config.read().unwrap();
let value = config
.get(key)
.ok_or_else(|| ConfigError::KeyNotFound(key.to_string()))?;
serde_json::from_value(value.clone())
.map_err(|e| ConfigError::DeserializationError(e.to_string()))
}
pub fn get_or<T: DeserializeOwned>(&self, key: &str, default: T) -> T {
self.get(key).unwrap_or(default)
}
pub fn get_string(&self, key: &str) -> Result<String> {
self.get(key)
}
pub fn get_int(&self, key: &str) -> Result<i64> {
self.get(key)
}
pub fn get_bool(&self, key: &str) -> Result<bool> {
self.get(key)
}
pub fn get_float(&self, key: &str) -> Result<f64> {
self.get(key)
}
pub fn has(&self, key: &str) -> bool {
let config = self.config.read().unwrap();
config.contains_key(key)
}
pub fn remove(&self, key: &str) {
let mut config = self.config.write().unwrap();
config.remove(key);
}
pub fn clear(&self) {
let mut config = self.config.write().unwrap();
config.clear();
}
pub fn keys(&self) -> Vec<String> {
let config = self.config.read().unwrap();
config.keys().cloned().collect()
}
pub fn merge(&self, other: &ConfigManager) -> Result<()> {
let other_config = other.config.read().unwrap();
let mut config = self.config.write().unwrap();
for (key, value) in other_config.iter() {
config.insert(key.clone(), value.clone());
}
Ok(())
}
pub fn load_validated<T: DeserializeOwned + Validate>(&self) -> Result<T> {
let config = self.config.read().unwrap();
let json_value =
serde_json::Value::Object(config.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
let validated: T = serde_json::from_value(json_value)
.map_err(|e| ConfigError::DeserializationError(e.to_string()))?;
validated.validate()?;
Ok(validated)
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new()
}
}
pub mod prelude {
pub use crate::ConfigManager;
pub use crate::config_service::ConfigService;
pub use crate::env::EnvLoader;
pub use crate::error::{ConfigError, Result};
pub use crate::loader::{ConfigLoader, FileFormat};
pub use crate::validation::{ConfigValidator, Validate};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_get() {
let manager = ConfigManager::new();
manager.set("test_key", "test_value").unwrap();
let value: String = manager.get("test_key").unwrap();
assert_eq!(value, "test_value");
}
#[test]
fn test_get_or_default() {
let manager = ConfigManager::new();
let value: String = manager.get_or("missing_key", "default_value".to_string());
assert_eq!(value, "default_value");
}
#[test]
fn test_has_key() {
let manager = ConfigManager::new();
manager.set("existing_key", "value").unwrap();
assert!(manager.has("existing_key"));
assert!(!manager.has("missing_key"));
}
#[test]
fn test_type_conversions() {
let manager = ConfigManager::new();
manager.set("string_key", "hello").unwrap();
manager.set("int_key", 42i64).unwrap();
manager.set("bool_key", true).unwrap();
manager.set("float_key", 3.15).unwrap();
assert_eq!(manager.get_string("string_key").unwrap(), "hello");
assert_eq!(manager.get_int("int_key").unwrap(), 42);
assert!(manager.get_bool("bool_key").unwrap());
assert_eq!(manager.get_float("float_key").unwrap(), 3.15);
}
#[test]
fn test_get_missing_key() {
let manager = ConfigManager::new();
let result: Result<String> = manager.get("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_set_overwrites() {
let manager = ConfigManager::new();
manager.set("key", "value1").unwrap();
manager.set("key", "value2").unwrap();
let value: String = manager.get("key").unwrap();
assert_eq!(value, "value2");
}
#[test]
fn test_multiple_keys() {
let manager = ConfigManager::new();
manager.set("key1", "value1").unwrap();
manager.set("key2", "value2").unwrap();
manager.set("key3", "value3").unwrap();
assert!(manager.has("key1"));
assert!(manager.has("key2"));
assert!(manager.has("key3"));
}
#[test]
fn test_nested_keys() {
let manager = ConfigManager::new();
manager.set("database.host", "localhost").unwrap();
manager.set("database.port", 5432i64).unwrap();
assert_eq!(manager.get_string("database.host").unwrap(), "localhost");
assert_eq!(manager.get_int("database.port").unwrap(), 5432);
}
#[test]
fn test_empty_string() {
let manager = ConfigManager::new();
manager.set("empty", "").unwrap();
let value: String = manager.get("empty").unwrap();
assert_eq!(value, "");
}
#[test]
fn test_zero_values() {
let manager = ConfigManager::new();
manager.set("zero_int", 0i64).unwrap();
manager.set("zero_float", 0.0).unwrap();
assert_eq!(manager.get_int("zero_int").unwrap(), 0);
assert_eq!(manager.get_float("zero_float").unwrap(), 0.0);
}
#[test]
fn test_negative_numbers() {
let manager = ConfigManager::new();
manager.set("negative_int", -42i64).unwrap();
manager.set("negative_float", -3.15).unwrap();
assert_eq!(manager.get_int("negative_int").unwrap(), -42);
assert_eq!(manager.get_float("negative_float").unwrap(), -3.15);
}
#[test]
fn test_bool_values() {
let manager = ConfigManager::new();
manager.set("true_val", true).unwrap();
manager.set("false_val", false).unwrap();
assert!(manager.get_bool("true_val").unwrap());
assert!(!manager.get_bool("false_val").unwrap());
}
#[test]
fn test_large_numbers() {
let manager = ConfigManager::new();
manager.set("large_int", i64::MAX).unwrap();
manager.set("large_float", f64::MAX).unwrap();
assert_eq!(manager.get_int("large_int").unwrap(), i64::MAX);
assert_eq!(manager.get_float("large_float").unwrap(), f64::MAX);
}
#[test]
fn test_special_characters_in_values() {
let manager = ConfigManager::new();
manager.set("special", "value!@#$%^&*()").unwrap();
let value: String = manager.get("special").unwrap();
assert_eq!(value, "value!@#$%^&*()");
}
#[test]
fn test_unicode_values() {
let manager = ConfigManager::new();
manager.set("unicode", "Hello δΈη π").unwrap();
let value: String = manager.get("unicode").unwrap();
assert_eq!(value, "Hello δΈη π");
}
#[test]
fn test_whitespace_values() {
let manager = ConfigManager::new();
manager.set("spaces", " value with spaces ").unwrap();
let value: String = manager.get("spaces").unwrap();
assert_eq!(value, " value with spaces ");
}
#[test]
fn test_newline_in_values() {
let manager = ConfigManager::new();
manager.set("multiline", "line1\nline2\nline3").unwrap();
let value: String = manager.get("multiline").unwrap();
assert!(value.contains("\n"));
}
#[test]
fn test_get_or_with_existing_key() {
let manager = ConfigManager::new();
manager.set("key", "actual").unwrap();
let value: String = manager.get_or("key", "default".to_string());
assert_eq!(value, "actual");
}
#[test]
fn test_clone_config_manager() {
let manager1 = ConfigManager::new();
manager1.set("key", "value").unwrap();
let manager2 = manager1.clone();
let value: String = manager2.get("key").unwrap();
assert_eq!(value, "value");
}
#[test]
fn test_concurrent_access() {
use std::sync::Arc;
use std::thread;
let manager = Arc::new(ConfigManager::new());
manager.set("counter", 0i64).unwrap();
let handles: Vec<_> = (0..10)
.map(|i| {
let manager = Arc::clone(&manager);
thread::spawn(move || {
manager.set(&format!("key{}", i), i).unwrap();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
for i in 0..10 {
assert!(manager.has(&format!("key{}", i)));
}
}
#[test]
fn test_case_sensitive_keys() {
let manager = ConfigManager::new();
manager.set("Key", "value1").unwrap();
manager.set("key", "value2").unwrap();
assert!(manager.has("Key"));
assert!(manager.has("key"));
assert_ne!(
manager.get_string("Key").unwrap(),
manager.get_string("key").unwrap()
);
}
}