Skip to main content

cachekit/backend/
mod.rs

1use std::collections::HashMap;
2use std::time::Duration;
3
4use async_trait::async_trait;
5
6use crate::error::BackendError;
7
8// ── HealthStatus ─────────────────────────────────────────────────────────────
9
10/// Describes the health of a backend at a point in time.
11#[derive(Debug, Clone)]
12pub struct HealthStatus {
13    /// Whether the backend is considered healthy.
14    pub is_healthy: bool,
15    /// Round-trip latency of the health check in milliseconds.
16    pub latency_ms: f64,
17    /// Human-readable name for this backend implementation.
18    pub backend_type: String,
19    /// Optional key-value details (pool size, version, etc.).
20    pub details: HashMap<String, String>,
21}
22
23// ── Backend trait ─────────────────────────────────────────────────────────────
24
25/// Async cache backend abstraction.
26///
27/// Implementors must be `Send + Sync` on native targets (unless the `unsync`
28/// feature is enabled). On `wasm32` targets or with `unsync`, `Send` is relaxed
29/// (`?Send`) because the runtime is single-threaded.
30#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
31#[async_trait]
32pub trait Backend: Send + Sync {
33    /// Retrieve the raw bytes stored under `key`, or `None` if absent.
34    async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, BackendError>;
35
36    /// Store `value` under `key`, optionally expiring after `ttl`.
37    async fn set(
38        &self,
39        key: &str,
40        value: Vec<u8>,
41        ttl: Option<Duration>,
42    ) -> Result<(), BackendError>;
43
44    /// Remove `key` and return `true` if it existed.
45    async fn delete(&self, key: &str) -> Result<bool, BackendError>;
46
47    /// Return `true` if `key` exists without fetching the value.
48    async fn exists(&self, key: &str) -> Result<bool, BackendError>;
49
50    /// Return health/status information for this backend.
51    async fn health(&self) -> Result<HealthStatus, BackendError>;
52}
53
54/// Async cache backend abstraction (`?Send` variant).
55///
56/// Active when compiling for `wasm32` or with the `unsync` feature.
57/// Identical API to the `Send + Sync` variant but without thread-safety bounds.
58#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
59#[async_trait(?Send)]
60pub trait Backend {
61    /// Retrieve the raw bytes stored under `key`, or `None` if absent.
62    async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, BackendError>;
63
64    /// Store `value` under `key`, optionally expiring after `ttl`.
65    async fn set(
66        &self,
67        key: &str,
68        value: Vec<u8>,
69        ttl: Option<Duration>,
70    ) -> Result<(), BackendError>;
71
72    /// Remove `key` and return `true` if it existed.
73    async fn delete(&self, key: &str) -> Result<bool, BackendError>;
74
75    /// Return `true` if `key` exists without fetching the value.
76    async fn exists(&self, key: &str) -> Result<bool, BackendError>;
77
78    /// Return health/status information for this backend.
79    async fn health(&self) -> Result<HealthStatus, BackendError>;
80}
81
82// ── TtlInspectable ───────────────────────────────────────────────────────────
83
84/// Optional extension for backends that can report the remaining TTL of a key.
85#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
86#[async_trait]
87pub trait TtlInspectable: Backend {
88    /// Return the remaining TTL for `key`, or `None` if the key does not exist
89    /// or has no expiry.
90    async fn ttl(&self, key: &str) -> Result<Option<Duration>, BackendError>;
91
92    /// Refresh the TTL on an existing key. Default: not supported.
93    async fn refresh_ttl(&self, _key: &str, _ttl: Duration) -> Result<bool, BackendError> {
94        Err(BackendError::permanent(
95            "refresh_ttl not supported by this backend",
96        ))
97    }
98}
99
100/// Optional extension for backends that can report the remaining TTL of a key (`?Send` variant).
101#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
102#[async_trait(?Send)]
103pub trait TtlInspectable: Backend {
104    /// Return the remaining TTL for `key`, or `None` if the key does not exist
105    /// or has no expiry.
106    async fn ttl(&self, key: &str) -> Result<Option<Duration>, BackendError>;
107
108    /// Refresh the TTL on an existing key. Default: not supported.
109    async fn refresh_ttl(&self, _key: &str, _ttl: Duration) -> Result<bool, BackendError> {
110        Err(BackendError::permanent(
111            "refresh_ttl not supported by this backend",
112        ))
113    }
114}
115
116// ── LockableBackend ─────────────────────────────────────────────────────────
117
118/// Optional extension for backends that support distributed locking.
119#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
120#[async_trait]
121pub trait LockableBackend: Backend {
122    /// Acquire a distributed lock. Returns lock_id if acquired, None if contested.
123    async fn acquire_lock(
124        &self,
125        key: &str,
126        timeout_ms: u64,
127    ) -> Result<Option<String>, BackendError>;
128    /// Release a distributed lock. Returns true if released.
129    async fn release_lock(&self, key: &str, lock_id: &str) -> Result<bool, BackendError>;
130}
131
132/// Optional extension for backends that support distributed locking (`?Send` variant).
133#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
134#[async_trait(?Send)]
135pub trait LockableBackend: Backend {
136    /// Acquire a distributed lock. Returns lock_id if acquired, None if contested.
137    async fn acquire_lock(
138        &self,
139        key: &str,
140        timeout_ms: u64,
141    ) -> Result<Option<String>, BackendError>;
142    /// Release a distributed lock. Returns true if released.
143    async fn release_lock(&self, key: &str, lock_id: &str) -> Result<bool, BackendError>;
144}
145
146// ── Feature-gated backend modules ─────────────────────────────────────────────
147
148/// HTTP backend for the cachekit.io SaaS API.
149#[cfg(feature = "cachekitio")]
150pub mod cachekitio;
151#[cfg(feature = "cachekitio")]
152mod cachekitio_lock;
153#[cfg(feature = "cachekitio")]
154mod cachekitio_ttl;
155
156/// Redis backend via the [`fred`](https://crates.io/crates/fred) client.
157#[cfg(feature = "redis")]
158pub mod redis;
159
160/// Cloudflare Workers backend using `worker::Fetch`.
161#[cfg(feature = "workers")]
162pub mod workers;