rapid-rs 0.5.0

Zero-config, batteries-included web framework for Rust - FastAPI meets Spring Boot
Documentation
//! In-memory cache implementation using Moka

use moka::future::Cache as MokaCache;
use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

use super::{CacheConfig, CacheStats};
use crate::error::ApiError;

pub struct MemoryCache {
    cache: MokaCache<String, Vec<u8>>,
    hits: Arc<AtomicU64>,
    misses: Arc<AtomicU64>,
}

impl MemoryCache {
    pub fn new(config: CacheConfig) -> Self {
        let cache = MokaCache::builder()
            .max_capacity(config.max_entries)
            .time_to_live(Duration::from_secs(config.default_ttl_seconds))
            .build();
        
        Self {
            cache,
            hits: Arc::new(AtomicU64::new(0)),
            misses: Arc::new(AtomicU64::new(0)),
        }
    }
    
    pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, ApiError> {
        match self.cache.get(key).await {
            Some(bytes) => {
                self.hits.fetch_add(1, Ordering::Relaxed);
                let value = serde_json::from_slice(&bytes)
                    .map_err(|e| ApiError::InternalServerError(
                        format!("Cache deserialization error: {}", e)
                    ))?;
                Ok(Some(value))
            }
            None => {
                self.misses.fetch_add(1, Ordering::Relaxed);
                Ok(None)
            }
        }
    }
    
    pub async fn set<T: Serialize + Send + Sync>(
        &self,
        key: &str,
        value: &T,
        _ttl: Duration,
    ) -> Result<(), ApiError> {
        let bytes = serde_json::to_vec(value)
            .map_err(|e| ApiError::InternalServerError(
                format!("Cache serialization error: {}", e)
            ))?;
        
        self.cache.insert(key.to_string(), bytes).await;
        Ok(())
    }
    
    pub async fn delete(&self, key: &str) -> Result<(), ApiError> {
        self.cache.invalidate(key).await;
        Ok(())
    }
    
    pub async fn exists(&self, key: &str) -> Result<bool, ApiError> {
        Ok(self.cache.get(key).await.is_some())
    }
    
    pub async fn clear(&self) -> Result<(), ApiError> {
        self.cache.invalidate_all();
        Ok(())
    }
    
    pub async fn stats(&self) -> Result<CacheStats, ApiError> {
        let hits = self.hits.load(Ordering::Relaxed);
        let misses = self.misses.load(Ordering::Relaxed);
        let total = hits + misses;
        let hit_rate = if total > 0 {
            hits as f64 / total as f64
        } else {
            0.0
        };
        
        Ok(CacheStats {
            hits,
            misses,
            entries: self.cache.entry_count(),
            hit_rate,
        })
    }
}