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;