#[cfg(feature = "dynamic-redis")]
use redis::aio::ConnectionManager;
#[cfg(feature = "dynamic-redis")]
use redis::{AsyncCommands, Client};
#[cfg(feature = "dynamic-redis")]
use crate::settings::dynamic::{DynamicBackend, DynamicError, DynamicResult};
#[cfg(feature = "dynamic-redis")]
use async_trait::async_trait;
#[derive(Clone)]
pub struct RedisSettingsBackend {
#[cfg(feature = "dynamic-redis")]
conn: ConnectionManager,
#[cfg(not(feature = "dynamic-redis"))]
_phantom: std::marker::PhantomData<()>,
}
impl RedisSettingsBackend {
#[cfg(feature = "dynamic-redis")]
pub async fn new(url: &str) -> Result<Self, String> {
let client =
Client::open(url).map_err(|e| format!("Failed to create Redis client: {}", e))?;
let conn = ConnectionManager::new(client)
.await
.map_err(|e| format!("Failed to connect to Redis: {}", e))?;
Ok(Self { conn })
}
#[cfg(not(feature = "dynamic-redis"))]
pub async fn new(_url: &str) -> Result<Self, String> {
Err("Redis backend not enabled. Enable the 'dynamic-redis' feature.".to_string())
}
#[cfg(feature = "dynamic-redis")]
fn prefixed_key(&self, key: &str) -> String {
format!("settings:{}", key)
}
#[cfg(feature = "dynamic-redis")]
pub async fn get(&self, key: &str) -> Result<Option<String>, String> {
let mut conn = self.conn.clone();
let prefixed_key = self.prefixed_key(key);
conn.get(&prefixed_key)
.await
.map_err(|e| format!("Failed to get setting: {}", e))
}
#[cfg(feature = "dynamic-redis")]
pub async fn set(&self, key: &str, value: &str, ttl: Option<u64>) -> Result<(), String> {
let mut conn = self.conn.clone();
let prefixed_key = self.prefixed_key(key);
if let Some(seconds) = ttl {
let _: () = conn
.set_ex(&prefixed_key, value, seconds)
.await
.map_err(|e| format!("Failed to set setting with TTL: {}", e))?;
} else {
let _: () = conn
.set(&prefixed_key, value)
.await
.map_err(|e| format!("Failed to set setting: {}", e))?;
}
Ok(())
}
#[cfg(feature = "dynamic-redis")]
pub async fn delete(&self, key: &str) -> Result<(), String> {
let mut conn = self.conn.clone();
let prefixed_key = self.prefixed_key(key);
let _: () = conn
.del(&prefixed_key)
.await
.map_err(|e| format!("Failed to delete setting: {}", e))?;
Ok(())
}
#[cfg(feature = "dynamic-redis")]
pub async fn exists(&self, key: &str) -> Result<bool, String> {
let mut conn = self.conn.clone();
let prefixed_key = self.prefixed_key(key);
conn.exists(&prefixed_key)
.await
.map_err(|e| format!("Failed to check setting existence: {}", e))
}
#[cfg(feature = "dynamic-redis")]
pub async fn keys(&self) -> Result<Vec<String>, String> {
let mut conn = self.conn.clone();
let pattern = "settings:*";
let prefixed_keys: Vec<String> = conn
.keys(pattern)
.await
.map_err(|e| format!("Failed to get keys: {}", e))?;
let keys = prefixed_keys
.into_iter()
.filter_map(|k| k.strip_prefix("settings:").map(|s| s.to_string()))
.collect();
Ok(keys)
}
}
#[cfg(feature = "dynamic-redis")]
#[async_trait]
impl DynamicBackend for RedisSettingsBackend {
async fn get(&self, key: &str) -> DynamicResult<Option<serde_json::Value>> {
let result = RedisSettingsBackend::get(self, key)
.await
.map_err(DynamicError::Backend)?;
match result {
Some(s) => {
let value = serde_json::from_str(&s).map_err(DynamicError::from)?;
Ok(Some(value))
}
None => Ok(None),
}
}
async fn set(
&self,
key: &str,
value: &serde_json::Value,
ttl: Option<u64>,
) -> DynamicResult<()> {
let value_str = serde_json::to_string(value).map_err(DynamicError::from)?;
RedisSettingsBackend::set(self, key, &value_str, ttl)
.await
.map_err(DynamicError::Backend)
}
async fn delete(&self, key: &str) -> DynamicResult<()> {
RedisSettingsBackend::delete(self, key)
.await
.map_err(DynamicError::Backend)
}
async fn exists(&self, key: &str) -> DynamicResult<bool> {
RedisSettingsBackend::exists(self, key)
.await
.map_err(DynamicError::Backend)
}
async fn keys(&self) -> DynamicResult<Vec<String>> {
RedisSettingsBackend::keys(self)
.await
.map_err(DynamicError::Backend)
}
}
#[cfg(all(test, not(feature = "dynamic-redis")))]
mod tests_no_feature {
use super::*;
#[tokio::test]
async fn test_redis_backend_disabled() {
let result = RedisSettingsBackend::new("redis://localhost:6379").await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Redis backend not enabled"));
}
}