Skip to main content

sockudo_core/
cache.rs

1use crate::error::Result;
2use async_trait::async_trait;
3use std::time::Duration;
4
5#[derive(Debug, Clone, Default, PartialEq, Eq)]
6pub struct CacheScanPage {
7    pub entries: Vec<(String, String)>,
8    pub next_cursor: Option<String>,
9}
10
11// Cache Manager Interface trait
12#[async_trait]
13pub trait CacheManager: Send + Sync {
14    /// Check if the given key exists in cache
15    async fn has(&self, key: &str) -> Result<bool>;
16
17    /// Get a key from the cache
18    /// Returns None if cache does not exist
19    async fn get(&self, key: &str) -> Result<Option<String>>;
20
21    /// Set or overwrite the value in the cache
22    async fn set(&self, key: &str, value: &str, ttl_seconds: u64) -> Result<()>;
23
24    /// Remove a key from the cache
25    async fn remove(&self, key: &str) -> Result<()>;
26
27    /// Disconnect the manager's made connections
28    async fn disconnect(&self) -> Result<()>;
29
30    /// Health check for the cache manager
31    async fn check_health(&self) -> Result<()> {
32        // Default implementation - always healthy for memory/no-op caches
33        Ok(())
34    }
35
36    async fn ttl(&self, key: &str) -> Result<Option<Duration>>;
37
38    /// Return up to `limit` unprefixed cache entries whose key starts with `prefix`.
39    ///
40    /// Implementations must bound the returned set by `limit`; callers use this
41    /// for low-frequency janitor work, not publish fan-out.
42    async fn scan_prefix(&self, prefix: &str, limit: usize) -> Result<Vec<(String, String)>> {
43        let _ = (prefix, limit);
44        Ok(Vec::new())
45    }
46
47    async fn scan_prefix_page(
48        &self,
49        prefix: &str,
50        cursor: Option<String>,
51        limit: usize,
52    ) -> Result<CacheScanPage> {
53        let _ = cursor;
54        Ok(CacheScanPage {
55            entries: self.scan_prefix(prefix, limit).await?,
56            next_cursor: None,
57        })
58    }
59
60    /// Atomically set a key only if it does not already exist. Returns `true`
61    /// if the key was set (i.e., it did not exist), `false` otherwise.
62    /// Default implementation falls back to non-atomic has+set.
63    async fn set_if_not_exists(&self, key: &str, value: &str, ttl_seconds: u64) -> Result<bool> {
64        if self.has(key).await? {
65            return Ok(false);
66        }
67        self.set(key, value, ttl_seconds).await?;
68        Ok(true)
69    }
70
71    async fn increment_by(&self, key: &str, delta: i64, ttl_seconds: u64) -> Result<i64> {
72        let current = self
73            .get(key)
74            .await?
75            .and_then(|value| value.parse::<i64>().ok())
76            .unwrap_or(0);
77        let next = current.saturating_add(delta);
78        self.set(key, &next.to_string(), ttl_seconds).await?;
79        Ok(next)
80    }
81}