secra-memory 0.1.1

A unified memory cache management library for plugin systems, built on top of moka
Documentation
use async_trait::async_trait;
use serde::Serialize;
use serde::de::DeserializeOwned;
/// PluginMemoryCache 实现
use std::sync::Arc;
use std::time::Duration;

use crate::error::CacheError;
use crate::cache::Cache;
use super::manager::MemoryManager;

/// Key 最大长度限制
const MAX_KEY_LENGTH: usize = 200;

/// 插件内存缓存实例
///
/// 每个插件拥有一个独立的 PluginMemoryCache 实例,所有操作自动添加命名空间前缀
pub struct PluginMemoryCache {
    /// MemoryManager 引用
    manager: Arc<MemoryManager>,

    /// 插件 ID
    plugin_id: String,

    /// 命名空间前缀
    namespace: String,
}

impl PluginMemoryCache {
    /// 创建新的 PluginMemoryCache 实例
    ///
    /// # Arguments
    /// * `manager` - MemoryManager 引用
    /// * `plugin_id` - 插件 ID
    ///
    /// # Returns
    /// * `Self` - PluginMemoryCache 实例
    pub(crate) fn new(manager: Arc<MemoryManager>, plugin_id: String) -> Self {
        // 优化:预分配容量,使用 push_str 比 format! 更快
        let system_name = manager.system_name();
        let capacity = system_name.len() + plugin_id.len() + 9; // "plugin::" = 9 chars
        let mut namespace = String::with_capacity(capacity);
        namespace.push_str(system_name);
        namespace.push_str(":plugin:");
        namespace.push_str(&plugin_id);
        namespace.push(':');
        
        Self {
            manager,
            plugin_id,
            namespace,
        }
    }

    /// 构建完整的 Key(添加命名空间前缀)
    /// 优化:使用 String::with_capacity 预分配容量,减少内存分配
    fn build_key(&self, business_key: &str) -> Result<String, CacheError> {
        // 验证业务 Key 格式
        self.validate_business_key(business_key)?;

        // 优化:预分配容量,避免多次分配,使用 push_str 比 format! 更快
        let capacity = self.namespace.len() + business_key.len();
        let mut full_key = String::with_capacity(capacity);
        full_key.push_str(&self.namespace);
        full_key.push_str(business_key);
        Ok(full_key)
    }

    /// 验证业务 Key 格式
    /// 优化:提前返回,减少不必要的检查,使用更高效的验证方法
    fn validate_business_key(&self, key: &str) -> Result<(), CacheError> {
        // 1. 不能为空(最快检查,优先执行)
        if key.is_empty() {
            return Err(CacheError::InvalidKey("Key 不能为空".to_string()));
        }

        // 2. 长度限制(快速检查,避免后续复杂操作)
        if key.len() > MAX_KEY_LENGTH {
            return Err(CacheError::InvalidKey(
                format!("Key 长度不能超过 {} 字符", MAX_KEY_LENGTH),
            ));
        }

        // 3. 不能包含命名空间分隔符(防止绕过隔离)
        // 优化:使用字节查找,比字符串 contains 更快
        if key.as_bytes().windows(7).any(|w| w == b"plugin:") {
            return Err(CacheError::InvalidKey(
                "Key 不能包含命名空间前缀".to_string(),
            ));
        }

        // 4. 字符限制(只允许字母、数字、下划线、连字符、冒号)
        // 优化:使用字节检查,比字符迭代更快,使用 match 可能更快
        if !key.as_bytes().iter().all(|&b| {
            matches!(b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' | b':')
        }) {
            return Err(CacheError::InvalidKey(
                "Key 包含非法字符,只允许字母、数字、下划线、连字符、冒号".to_string(),
            ));
        }

        Ok(())
    }

    /// 验证权限(确保只能操作自己的 Key)
    fn verify_permission(&self, full_key: &str) -> Result<(), CacheError> {
        if !full_key.starts_with(&self.namespace) {
            return Err(CacheError::PermissionDenied(
                "Key 不属于当前插件".to_string(),
            ));
        }
        Ok(())
    }
}

#[async_trait]
impl Cache for PluginMemoryCache {
    async fn get<T>(&self, key: &str) -> Result<Option<T>, CacheError>
    where
        T: DeserializeOwned,
    {
        let full_key = self.build_key(key)?;
        let cache = self.manager.get_cache();

        // 从内存缓存获取值
        let json_value = cache.get(&full_key).await;

        // 反序列化
        // 优化:使用 from_slice 可能更快,但需要先转换为 &[u8]
        // 当前保持 from_str 以保证代码简洁性
        match json_value {
            Some(json) => {
                let value: T = serde_json::from_str(&json)
                    .map_err(|e| CacheError::DeserializationFailed(e.to_string()))?;
                Ok(Some(value))
            }
            None => Ok(None),
        }
    }

    async fn set<T>(&self, key: &str, value: &T, _ttl: Option<Duration>) -> Result<(), CacheError>
    where
        T: Serialize + Sync,
    {
        // 优化:先构建 key,避免序列化失败后浪费计算
        let full_key = self.build_key(key)?;
        
        // 优化:使用 to_vec 然后转换为 String,或者直接使用 to_string
        // 对于大对象,可以考虑使用更快的序列化格式(如 bincode),但会失去可读性
        // 当前保持 JSON 格式以保证兼容性和可调试性
        let json_value = serde_json::to_string(value)
            .map_err(|e| CacheError::SerializationFailed(e.to_string()))?;

        let cache = self.manager.get_cache();

        // moka 的 future::Cache 不支持在 insert 时指定 TTL
        // 我们使用全局 TTL 配置,忽略传入的 ttl 参数
        // 注意:如果需要支持每个条目独立的 TTL,需要实现 Expiry trait
        // 优化:避免不必要的克隆,insert 会获取所有权
        cache.insert(full_key.clone(), json_value).await;

        // 更新索引(优化:DashMap 操作是同步的)
        self.manager
            .add_key_to_index(&self.plugin_id, &full_key);

        Ok(())
    }

    async fn delete(&self, key: &str) -> Result<bool, CacheError> {
        let full_key = self.build_key(key)?;

        // 验证权限
        self.verify_permission(&full_key)?;

        let cache = self.manager.get_cache();

        // 删除 Key
        let deleted = cache.remove(&full_key).await.is_some();

        // 从索引中移除(优化:DashMap 操作是同步的,无需 await)
        if deleted {
            self.manager
                .remove_key_from_index(&self.plugin_id, &full_key);
        }

        Ok(deleted)
    }

    async fn exists(&self, key: &str) -> Result<bool, CacheError> {
        let full_key = self.build_key(key)?;
        let cache = self.manager.get_cache();

        // 通过 get 方法检查 Key 是否存在(避免使用同步的 contains_key)
        let exists = cache.get(&full_key).await.is_some();

        Ok(exists)
    }

    async fn expire(&self, key: &str, _ttl: Duration) -> Result<bool, CacheError> {
        let full_key = self.build_key(key)?;
        let cache = self.manager.get_cache();

        // moka 的 future::Cache 不支持直接更新 TTL
        // 我们通过重新插入来"续期"(使用全局 TTL 配置)
        // 获取当前值并重新插入
        if let Some(value) = cache.get(&full_key).await {
            cache.insert(full_key, value).await;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    async fn ttl(&self, key: &str) -> Result<Option<Duration>, CacheError> {
        let full_key = self.build_key(key)?;
        let cache = self.manager.get_cache();

        // moka 不直接支持获取剩余 TTL,返回 None 表示存在但无法获取精确时间
        // 或者可以通过其他方式实现(如维护一个 TTL 映射表)
        // 这里简化处理:通过 get 检查 Key 是否存在
        if cache.get(&full_key).await.is_some() {
            // moka 不支持直接获取 TTL,返回 None 表示存在但无法确定剩余时间
            Ok(None)
        } else {
            Ok(None) // Key 不存在
        }
    }

    async fn clear(&self) -> Result<u64, CacheError> {
        // 清理当前插件的所有缓存
        self.manager.clear_plugin(&self.plugin_id).await
    }

    async fn clear_module(&self, module: &str) -> Result<u64, CacheError> {
        // 清理当前插件的指定模块缓存
        self.manager.clear_module(&self.plugin_id, module).await
    }
}