1use serde_json::Value;
16use std::collections::HashMap;
17use std::fmt;
18use std::future::Future;
19use std::sync::Mutex;
20use std::time::{Duration, Instant};
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct KvError(pub String);
25
26impl fmt::Display for KvError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 f.write_str(&self.0)
29 }
30}
31
32impl std::error::Error for KvError {}
33
34pub trait KvStore: Send + Sync + 'static {
39 fn get(&self, key: &[&str]) -> impl Future<Output = Result<Option<Value>, KvError>> + Send;
41
42 fn set(
45 &self,
46 key: &[&str],
47 value: Value,
48 ttl: Option<Duration>,
49 ) -> impl Future<Output = Result<(), KvError>> + Send;
50
51 fn insert(
56 &self,
57 key: &[&str],
58 value: Value,
59 ttl: Option<Duration>,
60 ) -> impl Future<Output = Result<bool, KvError>> + Send;
61
62 fn delete(&self, key: &[&str]) -> impl Future<Output = Result<(), KvError>> + Send;
64}
65
66#[derive(Debug, Default)]
78pub struct MemoryKvStore {
79 entries: Mutex<Entries>,
80 capacity: Option<usize>,
81}
82
83#[derive(Debug, Default)]
84struct Entries {
85 map: HashMap<Vec<String>, Entry>,
86 writes: usize,
88}
89
90#[derive(Debug)]
91struct Entry {
92 value: Value,
93 expires: Option<Instant>,
94}
95
96impl Entry {
97 fn live(&self, now: Instant) -> bool {
98 self.expires.is_none_or(|expires| expires > now)
99 }
100}
101
102const SWEEP_EVERY: usize = 1_024;
104
105impl Entries {
106 fn put(&mut self, key: Vec<String>, entry: Entry, capacity: Option<usize>) {
111 self.map.insert(key, entry);
112 self.writes += 1;
113 let now = Instant::now();
114 if self.writes >= SWEEP_EVERY.max(self.map.len() / 2) {
115 self.map.retain(|_, entry| entry.live(now));
116 self.writes = 0;
117 }
118 if let Some(capacity) = capacity
119 && self.map.len() > capacity
120 {
121 self.map.retain(|_, entry| entry.live(now));
122 self.shrink_to(capacity - capacity / 10);
123 }
124 }
125
126 fn shrink_to(&mut self, target: usize) {
130 let excess = self.map.len().saturating_sub(target);
131 if excess == 0 {
132 return;
133 }
134 let mut by_expiry: Vec<(Option<Instant>, &Vec<String>)> = self
137 .map
138 .iter()
139 .map(|(key, entry)| (entry.expires, key))
140 .collect();
141 by_expiry.select_nth_unstable_by(excess - 1, |a, b| match (a.0, b.0) {
142 (Some(a), Some(b)) => a.cmp(&b),
143 (Some(_), None) => std::cmp::Ordering::Less,
144 (None, Some(_)) => std::cmp::Ordering::Greater,
145 (None, None) => std::cmp::Ordering::Equal,
146 });
147 let doomed: Vec<Vec<String>> = by_expiry[..excess]
148 .iter()
149 .map(|(_, key)| (*key).clone())
150 .collect();
151 for key in doomed {
152 self.map.remove(&key);
153 }
154 }
155}
156
157impl MemoryKvStore {
158 #[must_use]
159 pub fn new() -> Self {
160 Self::default()
161 }
162
163 #[must_use]
168 pub fn with_capacity(capacity: usize) -> Self {
169 assert!(capacity > 0, "a memory kv store holds at least one entry");
170 Self {
171 entries: Mutex::default(),
172 capacity: Some(capacity),
173 }
174 }
175
176 #[must_use]
178 pub fn len(&self) -> usize {
179 self.lock().map.len()
180 }
181
182 #[must_use]
184 pub fn is_empty(&self) -> bool {
185 self.len() == 0
186 }
187
188 fn lock(&self) -> std::sync::MutexGuard<'_, Entries> {
189 self.entries.lock().expect("memory kv lock")
190 }
191}
192
193fn owned(key: &[&str]) -> Vec<String> {
194 key.iter().map(|segment| (*segment).to_owned()).collect()
195}
196
197fn expiry(ttl: Option<Duration>) -> Option<Instant> {
198 ttl.map(|ttl| Instant::now() + ttl)
199}
200
201impl KvStore for MemoryKvStore {
202 async fn get(&self, key: &[&str]) -> Result<Option<Value>, KvError> {
203 let mut entries = self.lock();
204 let key = owned(key);
205 match entries.map.get(&key) {
206 Some(entry) if entry.live(Instant::now()) => Ok(Some(entry.value.clone())),
207 Some(_) => {
208 entries.map.remove(&key);
209 Ok(None)
210 }
211 None => Ok(None),
212 }
213 }
214
215 async fn set(&self, key: &[&str], value: Value, ttl: Option<Duration>) -> Result<(), KvError> {
216 self.lock().put(
217 owned(key),
218 Entry {
219 value,
220 expires: expiry(ttl),
221 },
222 self.capacity,
223 );
224 Ok(())
225 }
226
227 async fn insert(
228 &self,
229 key: &[&str],
230 value: Value,
231 ttl: Option<Duration>,
232 ) -> Result<bool, KvError> {
233 let mut entries = self.lock();
234 let key = owned(key);
235 if entries
236 .map
237 .get(&key)
238 .is_some_and(|entry| entry.live(Instant::now()))
239 {
240 return Ok(false);
241 }
242 entries.put(
243 key,
244 Entry {
245 value,
246 expires: expiry(ttl),
247 },
248 self.capacity,
249 );
250 Ok(true)
251 }
252
253 async fn delete(&self, key: &[&str]) -> Result<(), KvError> {
254 self.lock().map.remove(&owned(key));
255 Ok(())
256 }
257}