use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::prelude::*;
use cloudillo_types::meta_adapter::MetaAdapter;
use super::types::{
DefinitionMatch, FrozenSettingsRegistry, Setting, SettingDefinition, SettingScope, SettingValue,
};
const DEFAULT_CACHE_CAPACITY: NonZeroUsize = match NonZeroUsize::new(100) {
Some(n) => n,
None => unreachable!(),
};
pub struct SettingsCache {
cache: Arc<parking_lot::Mutex<LruCache<(TnId, String), SettingValue>>>,
}
impl SettingsCache {
pub fn new(capacity: usize) -> Self {
let non_zero = NonZeroUsize::new(capacity).unwrap_or(DEFAULT_CACHE_CAPACITY);
Self { cache: Arc::new(parking_lot::Mutex::new(LruCache::new(non_zero))) }
}
pub fn get(&self, tn_id: TnId, key: &str) -> Option<SettingValue> {
let mut cache = self.cache.lock();
cache.get(&(tn_id, key.to_string())).cloned()
}
pub fn put(&self, tn_id: TnId, key: String, value: SettingValue) {
let mut cache = self.cache.lock();
cache.put((tn_id, key), value);
}
pub fn clear(&self) {
let mut cache = self.cache.lock();
cache.clear();
}
pub fn invalidate_key(&self, key: &str) {
let mut cache = self.cache.lock();
let to_remove: Vec<(TnId, String)> =
cache.iter().filter(|((_, k), _)| k == key).map(|(k, _)| k.clone()).collect();
for k in to_remove {
cache.pop(&k);
}
}
}
pub struct SettingsService {
registry: Arc<FrozenSettingsRegistry>,
cache: SettingsCache,
meta: Arc<dyn MetaAdapter>,
}
impl SettingsService {
pub fn new(
registry: Arc<FrozenSettingsRegistry>,
meta: Arc<dyn MetaAdapter>,
cache_size: usize,
) -> Self {
Self { registry, cache: SettingsCache::new(cache_size), meta }
}
pub async fn get(&self, tn_id: TnId, key: &str) -> ClResult<Option<SettingValue>> {
if let Some(value) = self.cache.get(tn_id, key) {
debug!("Setting cache hit: {}.{}", tn_id.0, key);
return Ok(Some(value));
}
if tn_id.0 != 0
&& let Some(value) = self.cache.get(TnId(0), key)
{
debug!("Setting cache hit (global fallback): {}", key);
return Ok(Some(value));
}
let m = self
.registry
.get_match(key)
.ok_or_else(|| Error::SettingNotFound(format!("Unknown setting: {}", key)))?;
if tn_id.0 != 0
&& let Some(json_value) = self.meta.read_setting(tn_id, key).await?
{
let value = serde_json::from_value::<SettingValue>(json_value)
.map_err(|e| Error::ValidationError(format!("Invalid setting value: {}", e)))?;
self.cache.put(tn_id, key.to_string(), value.clone());
return Ok(Some(value));
}
if let Some(json_value) = self.meta.read_setting(TnId(0), key).await? {
let value = serde_json::from_value::<SettingValue>(json_value)
.map_err(|e| Error::ValidationError(format!("Invalid setting value: {}", e)))?;
self.cache.put(TnId(0), key.to_string(), value.clone());
return Ok(Some(value));
}
let def = match m {
DefinitionMatch::Exact(d) => d,
DefinitionMatch::Wildcard(_) => return Ok(None),
};
match &def.default {
Some(default) => {
let value = default.clone();
self.cache.put(tn_id, key.to_string(), value.clone());
Ok(Some(value))
}
None => Err(Error::SettingNotFound(format!(
"Setting '{}' has no default and must be configured",
key
))),
}
}
pub async fn get_raw(&self, tn_id: TnId, key: &str) -> ClResult<Option<SettingValue>> {
self.registry
.get_match(key)
.ok_or_else(|| Error::SettingNotFound(format!("Unknown setting: {}", key)))?;
match self.meta.read_setting(tn_id, key).await? {
Some(json_value) => {
let value = serde_json::from_value::<SettingValue>(json_value)
.map_err(|e| Error::ValidationError(format!("Invalid setting value: {}", e)))?;
Ok(Some(value))
}
None => Ok(None),
}
}
pub async fn set<S: AsRef<str>>(
&self,
tn_id: TnId,
key: &str,
value: SettingValue,
roles: &[S],
) -> ClResult<Setting> {
let def = self
.registry
.get(key)
.ok_or_else(|| Error::ValidationError(format!("Unknown setting: {}", key)))?;
if !def.permission.check(roles) {
warn!("Permission denied for setting '{}': requires {:?}", key, def.permission);
return Err(Error::PermissionDenied);
}
let storage_tn_id = match (def.scope, tn_id.0) {
(SettingScope::System, _) => {
return Err(Error::PermissionDenied);
}
(SettingScope::Global | SettingScope::Tenant, 0) => {
if !roles.iter().any(|r| r.as_ref() == "SADM") {
return Err(Error::PermissionDenied);
}
TnId(0)
}
(SettingScope::Global, _) => {
if !roles.iter().any(|r| r.as_ref() == "SADM") {
return Err(Error::PermissionDenied);
}
TnId(0)
}
(SettingScope::Tenant, _) => {
tn_id
}
};
if let Some(default) = &def.default
&& !value.matches_type(default)
{
return Err(Error::ValidationError(format!(
"Type mismatch for setting '{}': expected {}, got {}",
key,
default.type_name(),
value.type_name()
)));
}
if let Some(validator) = &def.validator {
validator(&value)?;
}
let json_value = serde_json::to_value(&value)
.map_err(|e| Error::ValidationError(format!("Failed to serialize setting: {}", e)))?;
self.meta.update_setting(storage_tn_id, key, Some(json_value)).await?;
self.cache.invalidate_key(key);
info!("Setting '{}' updated for tn_id={}", key, storage_tn_id.0);
Ok(Setting {
key: key.to_string(),
value,
tn_id: storage_tn_id,
updated_at: cloudillo_types::types::Timestamp::now(),
})
}
pub async fn delete(&self, tn_id: TnId, key: &str) -> ClResult<bool> {
self.meta.update_setting(tn_id, key, None).await?;
self.cache.invalidate_key(key);
info!("Setting '{}' deleted for tn_id={}", key, tn_id.0);
Ok(true)
}
pub async fn clear<S: AsRef<str>>(&self, tn_id: TnId, key: &str, roles: &[S]) -> ClResult<()> {
let def = self
.registry
.get(key)
.ok_or_else(|| Error::ValidationError(format!("Unknown setting: {}", key)))?;
if !def.permission.check(roles) {
warn!(
"Permission denied for clearing setting '{}': requires {:?}",
key, def.permission
);
return Err(Error::PermissionDenied);
}
let storage_tn_id = match (def.scope, tn_id.0) {
(SettingScope::System, _) => return Err(Error::PermissionDenied),
(SettingScope::Global | SettingScope::Tenant, 0) => {
if !roles.iter().any(|r| r.as_ref() == "SADM") {
return Err(Error::PermissionDenied);
}
TnId(0)
}
(SettingScope::Global, _) => {
if !roles.iter().any(|r| r.as_ref() == "SADM") {
return Err(Error::PermissionDenied);
}
TnId(0)
}
(SettingScope::Tenant, _) => tn_id,
};
self.meta.update_setting(storage_tn_id, key, None).await?;
self.cache.invalidate_key(key);
info!("Setting '{}' cleared for tn_id={}", key, storage_tn_id.0);
Ok(())
}
pub async fn validate_required_settings(&self) -> ClResult<()> {
for def in self.registry.list() {
if def.optional || def.default.is_some() {
continue;
}
if self.meta.read_setting(TnId(0), &def.key).await?.is_none() {
return Err(Error::ValidationError(format!(
"Required setting '{}' is not configured",
def.key
)));
}
}
Ok(())
}
pub async fn get_string(&self, tn_id: TnId, key: &str) -> ClResult<String> {
match self.get(tn_id, key).await? {
Some(SettingValue::String(s)) => Ok(s),
Some(v) => Err(Error::ValidationError(format!(
"Setting '{}' is not a string, got {}",
key,
v.type_name()
))),
None => Err(Error::SettingNotFound(format!(
"Setting '{}' has no default and must be configured",
key
))),
}
}
pub async fn get_int(&self, tn_id: TnId, key: &str) -> ClResult<i64> {
match self.get(tn_id, key).await? {
Some(SettingValue::Int(i)) => Ok(i),
Some(v) => Err(Error::ValidationError(format!(
"Setting '{}' is not an integer, got {}",
key,
v.type_name()
))),
None => Err(Error::SettingNotFound(format!(
"Setting '{}' has no default and must be configured",
key
))),
}
}
pub async fn get_bool(&self, tn_id: TnId, key: &str) -> ClResult<bool> {
match self.get(tn_id, key).await? {
Some(SettingValue::Bool(b)) => Ok(b),
Some(v) => Err(Error::ValidationError(format!(
"Setting '{}' is not a boolean, got {}",
key,
v.type_name()
))),
None => Err(Error::SettingNotFound(format!(
"Setting '{}' has no default and must be configured",
key
))),
}
}
pub async fn get_json(&self, tn_id: TnId, key: &str) -> ClResult<serde_json::Value> {
match self.get(tn_id, key).await? {
Some(SettingValue::Json(j)) => Ok(j),
Some(v) => Err(Error::ValidationError(format!(
"Setting '{}' is not JSON, got {}",
key,
v.type_name()
))),
None => Err(Error::SettingNotFound(format!(
"Setting '{}' has no default and must be configured",
key
))),
}
}
pub async fn get_string_opt(&self, tn_id: TnId, key: &str) -> ClResult<Option<String>> {
match self.get(tn_id, key).await {
Ok(Some(SettingValue::String(s))) => Ok(Some(s)),
Ok(Some(v)) => Err(Error::ValidationError(format!(
"Setting '{}' is not a string, got {}",
key,
v.type_name()
))),
Ok(None) | Err(Error::SettingNotFound(_)) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn get_int_opt(&self, tn_id: TnId, key: &str) -> ClResult<Option<i64>> {
match self.get(tn_id, key).await {
Ok(Some(SettingValue::Int(i))) => Ok(Some(i)),
Ok(Some(v)) => Err(Error::ValidationError(format!(
"Setting '{}' is not an integer, got {}",
key,
v.type_name()
))),
Ok(None) | Err(Error::SettingNotFound(_)) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn get_bool_opt(&self, tn_id: TnId, key: &str) -> ClResult<Option<bool>> {
match self.get(tn_id, key).await {
Ok(Some(SettingValue::Bool(b))) => Ok(Some(b)),
Ok(Some(v)) => Err(Error::ValidationError(format!(
"Setting '{}' is not a boolean, got {}",
key,
v.type_name()
))),
Ok(None) | Err(Error::SettingNotFound(_)) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn get_json_opt(
&self,
tn_id: TnId,
key: &str,
) -> ClResult<Option<serde_json::Value>> {
match self.get(tn_id, key).await {
Ok(Some(SettingValue::Json(j))) => Ok(Some(j)),
Ok(Some(v)) => Err(Error::ValidationError(format!(
"Setting '{}' is not JSON, got {}",
key,
v.type_name()
))),
Ok(None) | Err(Error::SettingNotFound(_)) => Ok(None),
Err(e) => Err(e),
}
}
pub fn registry(&self) -> &Arc<FrozenSettingsRegistry> {
&self.registry
}
pub async fn list_by_prefix(
&self,
tn_id: TnId,
prefix: &str,
) -> ClResult<Vec<(String, SettingValue, &SettingDefinition)>> {
let prefixes = vec![format!("{}.", prefix)];
let global_settings = self.meta.list_settings(TnId(0), Some(&prefixes)).await?;
let tenant_settings = if tn_id.0 != 0 {
self.meta.list_settings(tn_id, Some(&prefixes)).await?
} else {
std::collections::HashMap::new()
};
let mut merged = global_settings;
merged.extend(tenant_settings);
let mut result = Vec::new();
for (key, json_value) in merged {
if let Some(definition) = self.registry.get(&key) {
let value = serde_json::from_value::<SettingValue>(json_value)
.map_err(|e| Error::ValidationError(format!("Invalid setting value: {}", e)))?;
result.push((key, value, definition));
}
}
Ok(result)
}
}