use std::collections::HashMap;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use serde::Serialize;
use serde::de::DeserializeOwned;
use tokio::sync::RwLock;
use crate::error::{ConfigError, ConfigResult};
use crate::filesystem::AsyncFileSystem;
use super::format::ConfigFormat;
use super::source::{
ConfigSource, ConfigSourcePriority, DefaultProvider, EnvironmentProvider, FileProvider,
MemoryProvider,
};
use super::traits::{ConfigProvider, Configurable};
use super::value::ConfigValue;
pub struct ConfigManager<T: Configurable> {
providers: Vec<Box<dyn ConfigProvider>>,
cache: Arc<RwLock<Option<T>>>,
_phantom: PhantomData<T>,
}
impl<T: Configurable + Clone> ConfigManager<T> {
pub fn builder() -> ConfigBuilder<T> {
ConfigBuilder::new()
}
fn new(providers: Vec<Box<dyn ConfigProvider>>) -> Self {
Self { providers, cache: Arc::new(RwLock::new(None)), _phantom: PhantomData }
}
pub async fn load(&self) -> ConfigResult<T> {
{
let cache = self.cache.read().await;
if let Some(config) = cache.as_ref() {
return Ok(config.clone());
}
}
let mut merged_value = ConfigValue::Map(HashMap::default());
for provider in &self.providers {
match provider.load().await {
Ok(value) => {
merged_value.merge(value);
}
Err(e) => {
log::warn!("Failed to load from provider '{}': {}", provider.name(), e);
}
}
}
let config: T = serde_json::from_value(serde_json::to_value(&merged_value)?)
.map_err(|e| ConfigError::parse("configuration", e.to_string()))?;
config.validate()?;
{
let mut cache = self.cache.write().await;
*cache = Some(config.clone());
}
Ok(config)
}
pub async fn reload(&self) -> ConfigResult<T> {
{
let mut cache = self.cache.write().await;
*cache = None;
}
self.load().await
}
pub async fn save(&self, config: &T) -> ConfigResult<()> {
config.validate()?;
let value = serde_json::to_value(config)?;
let config_value: ConfigValue = serde_json::from_value(value)?;
for provider in &self.providers {
if provider.supports_save() {
provider
.save(&config_value)
.await
.map_err(|e| ConfigError::provider(provider.name(), e.to_string()))?;
}
}
{
let mut cache = self.cache.write().await;
*cache = Some(config.clone());
}
Ok(())
}
pub async fn get<V: DeserializeOwned>(&self, key: &str) -> ConfigResult<Option<V>> {
let cache = self.cache.read().await;
let config = cache.as_ref().ok_or_else(|| ConfigError::other("No configuration loaded"))?;
let value = serde_json::to_value(config)?;
let config_value: ConfigValue = serde_json::from_value(value)?;
let parts: Vec<&str> = key.split('.').collect();
let mut current = &config_value;
for part in parts {
match current.get(part) {
Some(v) => current = v,
None => return Ok(None),
}
}
let result: V = serde_json::from_value(serde_json::to_value(current)?)
.map_err(|e| ConfigError::parse("value", e.to_string()))?;
Ok(Some(result))
}
pub async fn set<V: Serialize>(&self, key: &str, value: V) -> ConfigResult<()> {
let mut cache = self.cache.write().await;
let config = cache.as_mut().ok_or_else(|| ConfigError::other("No configuration loaded"))?;
let mut config_value: ConfigValue =
serde_json::from_value(serde_json::to_value(&*config)?)?;
let parts: Vec<&str> = key.split('.').collect();
let new_value: ConfigValue = serde_json::from_value(serde_json::to_value(&value)?)?;
let mut current = &mut config_value;
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
if let ConfigValue::Map(map) = current {
map.insert((*part).to_string(), new_value.clone());
} else {
return Err(ConfigError::type_error("map", current.type_name()));
}
} else {
if let ConfigValue::Map(map) = current {
let entry = map
.entry((*part).to_string())
.or_insert(ConfigValue::Map(HashMap::default()));
current = entry;
} else {
return Err(ConfigError::type_error("map", current.type_name()));
}
}
}
*config = serde_json::from_value(serde_json::to_value(&config_value)?)
.map_err(|e| ConfigError::parse("configuration", e.to_string()))?;
config.validate()?;
Ok(())
}
pub async fn merge(&self, other: T) -> ConfigResult<()> {
let mut cache = self.cache.write().await;
let config = cache.as_mut().ok_or_else(|| ConfigError::other("No configuration loaded"))?;
config.merge_with(other)?;
config.validate()?;
Ok(())
}
pub async fn validate(&self) -> ConfigResult<()> {
let cache = self.cache.read().await;
let config = cache.as_ref().ok_or_else(|| ConfigError::other("No configuration loaded"))?;
config.validate()
}
pub async fn clear_cache(&self) {
let mut cache = self.cache.write().await;
*cache = None;
}
}
pub struct ConfigBuilder<T: Configurable + Clone> {
sources: Vec<ConfigSource>,
_phantom: PhantomData<T>,
}
impl<T: Configurable + Clone> ConfigBuilder<T> {
fn new() -> Self {
Self { sources: Vec::new(), _phantom: PhantomData }
}
#[must_use]
pub fn with_defaults(mut self) -> Self
where
T: Default,
{
if let Some(defaults) = T::default_values() {
let value = match serde_json::to_value(defaults) {
Ok(serialized) => match serde_json::from_value(serialized) {
Ok(config_value) => config_value,
Err(e) => {
log::warn!(
"Failed to deserialize default configuration values: {e}. Using empty defaults."
);
ConfigValue::Map(HashMap::default())
}
},
Err(e) => {
log::warn!(
"Failed to serialize default configuration values: {e}. Using empty defaults."
);
ConfigValue::Map(HashMap::default())
}
};
self.sources.push(ConfigSource::defaults(value));
}
self
}
#[must_use]
pub fn with_file(mut self, path: impl AsRef<Path>) -> Self {
let path = path.as_ref();
let priority = if path.to_string_lossy().contains(".sublime") {
ConfigSourcePriority::Project
} else {
ConfigSourcePriority::Global
};
self.sources.push(ConfigSource::file(path, priority));
self
}
#[must_use]
pub fn with_file_priority(
mut self,
path: impl AsRef<Path>,
priority: ConfigSourcePriority,
) -> Self {
self.sources.push(ConfigSource::file(path, priority));
self
}
#[must_use]
pub fn with_env_prefix(mut self, prefix: impl Into<String>) -> Self {
self.sources.push(ConfigSource::environment(prefix, ConfigSourcePriority::Environment));
self
}
#[must_use]
pub fn with_source(mut self, source: ConfigSource) -> Self {
self.sources.push(source);
self
}
#[allow(clippy::needless_pass_by_value)] pub fn build<FS: AsyncFileSystem + Clone + 'static>(
self,
fs: FS,
) -> ConfigResult<ConfigManager<T>> {
let mut providers: Vec<Box<dyn ConfigProvider>> = Vec::new();
let mut sources = self.sources;
sources.sort_by_key(ConfigSource::priority);
for source in sources {
match source {
ConfigSource::File { path, format, priority } => {
let fmt =
format.or_else(|| ConfigFormat::from_path(&path)).ok_or_else(|| {
ConfigError::other(format!(
"Cannot determine format for file: {}",
path.display()
))
})?;
providers.push(Box::new(FileProvider::new(path, fmt, priority, fs.clone())));
}
ConfigSource::Environment { prefix, priority } => {
providers.push(Box::new(EnvironmentProvider::new(prefix, priority)));
}
ConfigSource::Default { values, priority } => {
providers.push(Box::new(DefaultProvider::new(values, priority)));
}
ConfigSource::Memory { values, priority } => {
providers.push(Box::new(MemoryProvider::new(values, priority)));
}
}
}
Ok(ConfigManager::new(providers))
}
}
impl<T: Configurable + Clone> Default for ConfigBuilder<T> {
fn default() -> Self {
Self::new()
}
}