use std::error::Error;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use serde::{Serialize, Deserialize};
use thiserror::Error;
use std::io;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigSource {
File(PathBuf),
Environment,
CommandLine,
UserInput,
Api,
Default,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConfigValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<ConfigValue>),
Map(HashMap<String, ConfigValue>),
Null,
}
#[derive(Debug, Clone)]
pub struct ConfigEntry {
pub key: String,
pub value: ConfigValue,
pub source: ConfigSource,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub sensitive: bool,
pub read_only: bool,
}
#[derive(Clone)]
pub enum ValidationRule {
Required,
MinValue(f64),
MaxValue(f64),
MinLength(usize),
MaxLength(usize),
Pattern(String),
Enum(Vec<ConfigValue>),
Custom(Arc<dyn Fn(&ConfigValue) -> Result<(), ValidationError> + Send + Sync>),
}
impl std::fmt::Debug for ValidationRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationRule::Required => write!(f, "ValidationRule::Required"),
ValidationRule::MinValue(val) => write!(f, "ValidationRule::MinValue({})", val),
ValidationRule::MaxValue(val) => write!(f, "ValidationRule::MaxValue({})", val),
ValidationRule::MinLength(len) => write!(f, "ValidationRule::MinLength({})", len),
ValidationRule::MaxLength(len) => write!(f, "ValidationRule::MaxLength({})", len),
ValidationRule::Pattern(pattern) => write!(f, "ValidationRule::Pattern({})", pattern),
ValidationRule::Enum(values) => write!(f, "ValidationRule::Enum({:?})", values),
ValidationRule::Custom(_) => write!(f, "ValidationRule::Custom(<function>)"),
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigChangeEvent {
pub key: String,
pub old_value: Option<ConfigValue>,
pub new_value: ConfigValue,
pub source: ConfigSource,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Configuration key not found: {0}")]
KeyNotFound(String),
#[error("Configuration validation error: {0}")]
ValidationError(#[from] ValidationError),
#[error("Configuration loading error: {0}")]
LoadError(String),
#[error("Configuration saving error: {0}")]
SaveError(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
}
#[derive(Debug, Error)]
pub enum ValidationError {
#[error("Required field is missing: {0}")]
RequiredField(String),
#[error("Value is less than minimum: {0} < {1}")]
LessThanMinimum(f64, f64),
#[error("Value is greater than maximum: {0} > {1}")]
GreaterThanMaximum(f64, f64),
#[error("String is shorter than minimum length: {0} < {1}")]
ShorterThanMinLength(usize, usize),
#[error("String is longer than maximum length: {0} > {1}")]
LongerThanMaxLength(usize, usize),
#[error("Pattern does not match: {0}")]
PatternMismatch(String),
#[error("Value is not in allowed enumeration")]
NotInEnum,
#[error("Custom validation error: {0}")]
Custom(String),
}
pub type ConfigChangeListener = Arc<dyn Fn(&ConfigChangeEvent) -> () + Send + Sync>;
pub struct ConfigManager {
entries: RwLock<HashMap<String, ConfigEntry>>,
validation_rules: RwLock<HashMap<String, Vec<ValidationRule>>>,
listeners: RwLock<Vec<ConfigChangeListener>>,
history: RwLock<Vec<ConfigChangeEvent>>,
max_history_size: usize,
}
impl ConfigManager {
pub fn new() -> Self {
Self {
entries: RwLock::new(HashMap::new()),
validation_rules: RwLock::new(HashMap::new()),
listeners: RwLock::new(Vec::new()),
history: RwLock::new(Vec::new()),
max_history_size: 100,
}
}
pub fn load_from_file(&self, path: &PathBuf) -> Result<(), ConfigError> {
let config_str = std::fs::read_to_string(path)
.map_err(|e| ConfigError::LoadError(format!("Failed to read file: {}", e)))?;
let extension = path.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
let config_map: HashMap<String, ConfigValue> = match extension {
"json" => serde_json::from_str(&config_str)
.map_err(|e| ConfigError::DeserializationError(format!("JSON error: {}", e)))?,
"yaml" | "yml" => serde_yaml::from_str(&config_str)
.map_err(|e| ConfigError::DeserializationError(format!("YAML error: {}", e)))?,
"toml" => toml::from_str(&config_str)
.map_err(|e| ConfigError::DeserializationError(format!("TOML error: {}", e)))?,
_ => return Err(ConfigError::LoadError(format!("Unsupported file extension: {}", extension))),
};
self.set_values_from_map(config_map, ConfigSource::File(path.clone()))
}
pub fn load_from_env(&self, prefix: &str) -> Result<(), ConfigError> {
let mut config_map = HashMap::new();
for (key, value) in std::env::vars() {
if key.starts_with(prefix) {
let config_key = key[prefix.len()..].to_lowercase();
config_map.insert(config_key, ConfigValue::String(value));
}
}
self.set_values_from_map(config_map, ConfigSource::Environment)
}
pub fn set_values_from_map(
&self,
values: HashMap<String, ConfigValue>,
source: ConfigSource
) -> Result<(), ConfigError> {
for (key, value) in values {
self.set_value(&key, value, source.clone())?;
}
Ok(())
}
pub fn set_value(
&self,
key: &str,
value: ConfigValue,
source: ConfigSource
) -> Result<(), ConfigError> {
self.validate_value(key, &value)?;
let mut entries = self.entries.write()?;
let old_value = entries.get(key).map(|entry| entry.value.clone());
let entry = ConfigEntry {
key: key.to_string(),
value: value.clone(),
source: source.clone(),
updated_at: chrono::Utc::now(),
sensitive: false, read_only: false, };
entries.insert(key.to_string(), entry);
let event = ConfigChangeEvent {
key: key.to_string(),
old_value,
new_value: value,
source,
timestamp: chrono::Utc::now(),
};
self.add_to_history(event.clone());
self.notify_listeners(&event);
Ok(())
}
fn add_to_history(&self, event: ConfigChangeEvent) {
let mut history = self.history.write()?;
history.push(event);
if history.len() > self.max_history_size {
let drain_count = history.len() - self.max_history_size;
history.drain(0..drain_count);
}
}
fn notify_listeners(&self, event: &ConfigChangeEvent) {
let listeners = self.listeners.read()?;
for listener in listeners.iter() {
listener(event);
}
}
pub fn get_value(&self, key: &str) -> Result<ConfigValue, ConfigError> {
let entries = self.entries.read()?;
entries.get(key)
.map(|entry| entry.value.clone())
.ok_or(ConfigError::KeyNotFound(key.to_string()))
}
pub fn has_key(&self, key: &str) -> bool {
let entries = self.entries.read()?;
entries.contains_key(key)
}
pub fn get_string(&self, key: &str) -> Result<String, ConfigError> {
match self.get_value(key)? {
ConfigValue::String(value) => Ok(value),
_ => Err(ConfigError::ValidationError(ValidationError::Custom(
format!("Configuration value is not a string: {}", key)
))),
}
}
pub fn get_integer(&self, key: &str) -> Result<i64, ConfigError> {
match self.get_value(key)? {
ConfigValue::Integer(value) => Ok(value),
_ => Err(ConfigError::ValidationError(ValidationError::Custom(
format!("Configuration value is not an integer: {}", key)
))),
}
}
pub fn get_float(&self, key: &str) -> Result<f64, ConfigError> {
match self.get_value(key)? {
ConfigValue::Float(value) => Ok(value),
ConfigValue::Integer(value) => Ok(value as f64),
_ => Err(ConfigError::ValidationError(ValidationError::Custom(
format!("Configuration value is not a float: {}", key)
))),
}
}
pub fn get_boolean(&self, key: &str) -> Result<bool, ConfigError> {
match self.get_value(key)? {
ConfigValue::Boolean(value) => Ok(value),
_ => Err(ConfigError::ValidationError(ValidationError::Custom(
format!("Configuration value is not a boolean: {}", key)
))),
}
}
pub fn add_validation_rule(&self, key: &str, rule: ValidationRule) {
let mut rules = self.validation_rules.write()?;
let key_rules = rules.entry(key.to_string()).or_insert_with(Vec::new);
key_rules.push(rule);
}
fn validate_value(&self, key: &str, value: &ConfigValue) -> Result<(), ConfigError> {
let rules = self.validation_rules.read()?;
if let Some(key_rules) = rules.get(key) {
for rule in key_rules {
match rule {
ValidationRule::Required => {
if let ConfigValue::Null = value {
return Err(ConfigError::ValidationError(ValidationError::RequiredField(
key.to_string()
)));
}
},
ValidationRule::MinValue(min) => {
let value_f64 = match value {
ConfigValue::Integer(i) => *i as f64,
ConfigValue::Float(f) => *f,
_ => continue,
};
if value_f64 < *min {
return Err(ConfigError::ValidationError(ValidationError::LessThanMinimum(
value_f64, *min
)));
}
},
ValidationRule::MaxValue(max) => {
let value_f64 = match value {
ConfigValue::Integer(i) => *i as f64,
ConfigValue::Float(f) => *f,
_ => continue,
};
if value_f64 > *max {
return Err(ConfigError::ValidationError(ValidationError::GreaterThanMaximum(
value_f64, *max
)));
}
},
ValidationRule::MinLength(min) => {
if let ConfigValue::String(s) = value {
if s.len() < *min {
return Err(ConfigError::ValidationError(ValidationError::ShorterThanMinLength(
s.len(), *min
)));
}
}
},
ValidationRule::MaxLength(max) => {
if let ConfigValue::String(s) = value {
if s.len() > *max {
return Err(ConfigError::ValidationError(ValidationError::LongerThanMaxLength(
s.len(), *max
)));
}
}
},
ValidationRule::Pattern(pattern) => {
if let ConfigValue::String(s) = value {
let regex = regex::Regex::new(pattern)
.map_err(|e| ConfigError::ValidationError(ValidationError::Custom(
format!("Invalid regex pattern: {}", e)
)))?;
if !regex.is_match(s) {
return Err(ConfigError::ValidationError(ValidationError::PatternMismatch(
s.clone()
)));
}
}
},
ValidationRule::Enum(allowed_values) => {
if !allowed_values.contains(value) {
return Err(ConfigError::ValidationError(ValidationError::NotInEnum));
}
},
ValidationRule::Custom(validator) => {
if let Err(e) = validator(value) {
return Err(ConfigError::ValidationError(e));
}
},
}
}
}
Ok(())
}
pub fn add_listener(&self, listener: ConfigChangeListener) {
let mut listeners = self.listeners.write()?;
listeners.push(listener);
}
pub fn get_history(&self) -> Vec<ConfigChangeEvent> {
let history = self.history.read()?;
history.clone()
}
pub fn save_to_file(&self, path: &PathBuf) -> Result<(), ConfigError> {
let entries = self.entries.read()?;
let mut config_map = HashMap::new();
for (key, entry) in entries.iter() {
if !entry.sensitive {
config_map.insert(key.clone(), entry.value.clone());
}
}
let extension = path.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
let config_str = match extension {
"json" => serde_json::to_string_pretty(&config_map)
.map_err(|e| ConfigError::SerializationError(format!("JSON error: {}", e)))?,
"yaml" | "yml" => serde_yaml::to_string(&config_map)
.map_err(|e| ConfigError::SerializationError(format!("YAML error: {}", e)))?,
"toml" => toml::to_string(&config_map)
.map_err(|e| ConfigError::SerializationError(format!("TOML error: {}", e)))?,
_ => return Err(ConfigError::SaveError(format!("Unsupported file extension: {}", extension))),
};
std::fs::write(path, config_str)
.map_err(|e| ConfigError::SaveError(format!("Failed to write file: {}", e)))?;
Ok(())
}
pub fn mark_as_sensitive(&self, key: &str, sensitive: bool) -> Result<(), ConfigError> {
let mut entries = self.entries.write()?;
let entry = entries.get_mut(key)
.ok_or(ConfigError::KeyNotFound(key.to_string()))?;
entry.sensitive = sensitive;
Ok(())
}
pub fn mark_as_read_only(&self, key: &str, read_only: bool) -> Result<(), ConfigError> {
let mut entries = self.entries.write()?;
let entry = entries.get_mut(key)
.ok_or(ConfigError::KeyNotFound(key.to_string()))?;
entry.read_only = read_only;
Ok(())
}
pub fn get_keys(&self) -> Vec<String> {
let entries = self.entries.read()?;
entries.keys().cloned().collect()
}
pub fn reset_to_default(&self, key: &str) -> Result<(), ConfigError> {
let entries = self.entries.read()?;
let default_entry = entries.values()
.find(|entry| entry.key == key && entry.source == ConfigSource::Default);
if let Some(default_entry) = default_entry {
let default_value = default_entry.value.clone();
drop(entries);
self.set_value(key, default_value, ConfigSource::Default)?;
Ok(())
} else {
Err(ConfigError::KeyNotFound(format!("No default value for key: {}", key)))
}
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new()
}
}
pub static CONFIG_MANAGER: once_cell::sync::Lazy<ConfigManager> = once_cell::sync::Lazy::new(|| {
ConfigManager::new()
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_config_operations() {
let config = ConfigManager::new();
config.set_value(
"test.key",
ConfigValue::String("test value".to_string()),
ConfigSource::Default
)?;
let value = config.get_value("test.key")?;
assert!(matches!(value, ConfigValue::String(s) if s == "test value"));
assert!(config.has_key("test.key"));
assert!(!config.has_key("non.existent.key"));
let string_value = config.get_string("test.key")?;
assert_eq!(string_value, "test value");
}
#[test]
fn test_validation_rules() {
let config = ConfigManager::new();
config.add_validation_rule("min.value", ValidationRule::MinValue(10.0));
config.add_validation_rule("max.length", ValidationRule::MaxLength(5));
assert!(config.set_value(
"min.value",
ConfigValue::Integer(5),
ConfigSource::Default
).is_err());
assert!(config.set_value(
"min.value",
ConfigValue::Integer(15),
ConfigSource::Default
).is_ok());
assert!(config.set_value(
"max.length",
ConfigValue::String("123456".to_string()),
ConfigSource::Default
).is_err());
assert!(config.set_value(
"max.length",
ConfigValue::String("12345".to_string()),
ConfigSource::Default
).is_ok());
}
#[test]
fn test_configuration_history() {
let config = ConfigManager::new();
config.set_value(
"history.test",
ConfigValue::Integer(1),
ConfigSource::Default
)?;
config.set_value(
"history.test",
ConfigValue::Integer(2),
ConfigSource::Default
)?;
config.set_value(
"history.test",
ConfigValue::Integer(3),
ConfigSource::Default
)?;
let history = config.get_history();
assert_eq!(history.len(), 3);
assert_eq!(history[0].key, "history.test");
assert_eq!(history[0].old_value, None);
assert!(matches!(history[0].new_value, ConfigValue::Integer(1)));
assert_eq!(history[1].key, "history.test");
assert!(matches!(history[1].old_value, Some(ConfigValue::Integer(1))));
assert!(matches!(history[1].new_value, ConfigValue::Integer(2)));
assert_eq!(history[2].key, "history.test");
assert!(matches!(history[2].old_value, Some(ConfigValue::Integer(2))));
assert!(matches!(history[2].new_value, ConfigValue::Integer(3)));
}
}