1use std::sync::Arc;
14use std::time::Duration;
15
16use async_trait::async_trait;
17use dashmap::DashMap;
18use serde::de::DeserializeOwned;
19use serde::{Deserialize, Serialize};
20
21use crate::entry::Entry;
22use crate::error::{Error, Result};
23use crate::tags::{Tag, collect_tags};
24use crate::time::{Clock, Timestamp};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct DistributedEntry<V> {
30 pub value: V,
32 pub created_ticks: i64,
34 pub logical_expiration_ticks: i64,
36 pub physical_expiration_ticks: i64,
38 pub is_from_fail_safe: bool,
40 pub etag: Option<String>,
42 pub last_modified_ticks: Option<i64>,
44 pub tags: Vec<String>,
46}
47
48impl<V: Clone> DistributedEntry<V> {
49 #[must_use]
51 pub fn from_entry(entry: &Entry<V>) -> Self {
52 let meta = entry.meta();
53 Self {
54 value: entry.value_cloned(),
55 created_ticks: meta.created().ticks(),
56 logical_expiration_ticks: meta.logical_expiration().ticks(),
57 physical_expiration_ticks: meta.physical_expiration().ticks(),
58 is_from_fail_safe: meta.is_from_fail_safe(),
59 etag: meta.etag().map(str::to_owned),
60 last_modified_ticks: meta.last_modified().map(Timestamp::ticks),
61 tags: meta.tags().iter().map(|t| t.as_str().to_owned()).collect(),
62 }
63 }
64
65 #[must_use]
67 pub fn into_entry(self, now: Timestamp) -> Entry<V> {
68 let tags: Box<[Tag]> = collect_tags(self.tags);
69 Entry::rehydrate(
70 self.value,
71 Timestamp::from_ticks(self.created_ticks),
72 Timestamp::from_ticks(self.logical_expiration_ticks),
73 Timestamp::from_ticks(self.physical_expiration_ticks),
74 self.is_from_fail_safe,
75 self.etag,
76 self.last_modified_ticks.map(Timestamp::from_ticks),
77 tags,
78 now,
79 )
80 }
81}
82
83pub trait DistributedSerializer<V>: Send + Sync {
89 fn serialize(&self, entry: &DistributedEntry<V>) -> Result<Vec<u8>>;
94
95 fn deserialize(&self, bytes: &[u8]) -> Result<DistributedEntry<V>>;
100}
101
102#[derive(Debug, Clone, Copy, Default)]
104pub struct JsonSerializer;
105
106impl<V> DistributedSerializer<V> for JsonSerializer
107where
108 V: Serialize + DeserializeOwned,
109{
110 fn serialize(&self, entry: &DistributedEntry<V>) -> Result<Vec<u8>> {
111 serde_json::to_vec(entry).map_err(|e| Error::Serialization(e.to_string()))
112 }
113
114 fn deserialize(&self, bytes: &[u8]) -> Result<DistributedEntry<V>> {
115 serde_json::from_slice(bytes).map_err(|e| Error::Deserialization(e.to_string()))
116 }
117}
118
119#[async_trait]
124pub trait DistributedCache: Send + Sync {
125 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
130
131 async fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<()>;
136
137 async fn remove(&self, key: &str) -> Result<()>;
142}
143
144#[derive(Clone)]
149pub struct InMemoryDistributedCache {
150 map: Arc<DashMap<String, StoredBytes>>,
151 clock: Arc<dyn Clock>,
152}
153
154#[derive(Clone)]
155struct StoredBytes {
156 bytes: Vec<u8>,
157 expires_at: Option<Timestamp>,
158}
159
160impl InMemoryDistributedCache {
161 #[must_use]
163 pub fn new(clock: Arc<dyn Clock>) -> Self {
164 Self {
165 map: Arc::new(DashMap::new()),
166 clock,
167 }
168 }
169}
170
171#[async_trait]
172impl DistributedCache for InMemoryDistributedCache {
173 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
174 let now = self.clock.now();
175 let hit = self.map.get(key).and_then(|stored| {
177 if stored.expires_at.is_none_or(|exp| now.is_before(exp)) {
178 Some(stored.bytes.clone())
179 } else {
180 None
181 }
182 });
183 if hit.is_none() {
184 self.map.remove(key);
186 }
187 Ok(hit)
188 }
189
190 async fn set(&self, key: &str, value: Vec<u8>, ttl: Option<Duration>) -> Result<()> {
191 let expires_at = ttl.map(|d| self.clock.now().saturating_add(d));
192 self.map.insert(
193 key.to_owned(),
194 StoredBytes {
195 bytes: value,
196 expires_at,
197 },
198 );
199 Ok(())
200 }
201
202 async fn remove(&self, key: &str) -> Result<()> {
203 self.map.remove(key);
204 Ok(())
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::time::ManualClock;
212
213 #[tokio::test]
214 async fn in_memory_l2_round_trips_and_expires() {
215 let clock = Arc::new(ManualClock::default());
216 let dyn_clock: Arc<dyn Clock> = clock.clone();
217 let l2 = InMemoryDistributedCache::new(dyn_clock);
218
219 l2.set("k", b"hello".to_vec(), Some(Duration::from_secs(10)))
220 .await
221 .unwrap();
222 assert_eq!(l2.get("k").await.unwrap(), Some(b"hello".to_vec()));
223
224 clock.advance(Duration::from_secs(11));
225 assert_eq!(l2.get("k").await.unwrap(), None);
226 }
227
228 #[test]
229 fn json_serializer_round_trips_envelope() {
230 let entry = DistributedEntry {
231 value: "v".to_owned(),
232 created_ticks: 1,
233 logical_expiration_ticks: 2,
234 physical_expiration_ticks: 3,
235 is_from_fail_safe: false,
236 etag: Some("e".to_owned()),
237 last_modified_ticks: None,
238 tags: vec!["t".to_owned()],
239 };
240 let ser = JsonSerializer;
241 let bytes = DistributedSerializer::<String>::serialize(&ser, &entry).unwrap();
242 let back = DistributedSerializer::<String>::deserialize(&ser, &bytes).unwrap();
243 assert_eq!(back.value, "v");
244 assert_eq!(back.tags, vec!["t".to_owned()]);
245 }
246}