1use std::borrow::Cow;
8use std::pin::Pin;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::time::Duration;
12use std::{collections::HashMap, path::PathBuf};
13use std::{env, fmt};
14
15use crate::CancellationToken;
16use crate::transports::etcd as etcd_transport;
17use async_trait::async_trait;
18use futures::StreamExt;
19use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode};
20use serde::{Deserialize, Serialize};
21
22mod mem;
23pub use mem::MemoryStore;
24mod nats;
25pub use nats::NATSStore;
26mod etcd;
27pub use etcd::EtcdStore;
28mod file;
29pub use file::FileStore;
30
31const WATCH_SEND_TIMEOUT: Duration = Duration::from_millis(1000);
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Key(String);
37
38impl Key {
39 pub fn new(s: String) -> Key {
40 Key(s)
41 }
42
43 pub fn from_url_safe(s: &str) -> Key {
46 Key(percent_decode_str(s).decode_utf8_lossy().to_string())
47 }
48
49 pub fn url_safe(&self) -> Cow<'_, str> {
52 percent_encode(self.0.as_bytes(), NON_ALPHANUMERIC).into()
53 }
54}
55
56impl From<&str> for Key {
57 fn from(s: &str) -> Key {
58 Key::new(s.to_string())
59 }
60}
61
62impl fmt::Display for Key {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(f, "{}", self.0)
65 }
66}
67
68impl AsRef<str> for Key {
69 fn as_ref(&self) -> &str {
70 &self.0
71 }
72}
73
74impl From<&Key> for String {
75 fn from(k: &Key) -> String {
76 k.0.clone()
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub struct KeyValue {
82 key: Key,
83 value: bytes::Bytes,
84}
85
86impl KeyValue {
87 pub fn new(key: Key, value: bytes::Bytes) -> Self {
88 KeyValue { key, value }
89 }
90
91 pub fn key(&self) -> String {
92 self.key.clone().to_string()
93 }
94
95 pub fn key_str(&self) -> &str {
96 self.key.as_ref()
97 }
98
99 pub fn value(&self) -> &[u8] {
100 &self.value
101 }
102
103 pub fn value_str(&self) -> anyhow::Result<&str> {
104 std::str::from_utf8(self.value()).map_err(From::from)
105 }
106}
107
108#[derive(Debug, Clone, PartialEq)]
109pub enum WatchEvent {
110 Put(KeyValue),
111 Delete(Key),
112 Resync(HashMap<Key, bytes::Bytes>),
113}
114
115#[async_trait]
116pub trait Store: Send + Sync {
117 type Bucket: Bucket + Send + Sync + 'static;
118
119 async fn get_or_create_bucket(
120 &self,
121 bucket_name: &str,
122 ttl: Option<Duration>,
124 ) -> Result<Self::Bucket, StoreError>;
125
126 async fn get_bucket(&self, bucket_name: &str) -> Result<Option<Self::Bucket>, StoreError>;
127
128 fn connection_id(&self) -> u64;
129
130 fn shutdown(&self);
131}
132
133#[derive(Clone, Debug, Default)]
134pub enum Selector {
135 Etcd(Box<etcd_transport::ClientOptions>),
137 File(PathBuf),
138 #[default]
139 Memory,
140 }
142
143impl fmt::Display for Selector {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Selector::Etcd(opts) => {
147 let urls = opts.etcd_url.join(",");
148 write!(f, "Etcd({urls})")
149 }
150 Selector::File(path) => write!(f, "File({})", path.display()),
151 Selector::Memory => write!(f, "Memory"),
152 }
153 }
154}
155
156impl FromStr for Selector {
157 type Err = anyhow::Error;
158
159 fn from_str(s: &str) -> anyhow::Result<Selector> {
160 match s {
161 "etcd" => Ok(Self::Etcd(Box::default())),
162 "file" => {
163 let root = env::var("DYN_FILE_KV")
164 .map(PathBuf::from)
165 .unwrap_or_else(|_| env::temp_dir().join("dynamo_store_kv"));
166 Ok(Self::File(root))
167 }
168 "mem" => Ok(Self::Memory),
169 x => anyhow::bail!("Unknown key-value store type '{x}'"),
170 }
171 }
172}
173
174impl TryFrom<String> for Selector {
175 type Error = anyhow::Error;
176
177 fn try_from(s: String) -> anyhow::Result<Selector> {
178 s.parse()
179 }
180}
181
182#[allow(clippy::large_enum_variant)]
183enum KeyValueStoreEnum {
184 Memory(MemoryStore),
185 Nats(NATSStore),
186 Etcd(EtcdStore),
187 File(FileStore),
188}
189
190impl KeyValueStoreEnum {
191 async fn get_or_create_bucket(
192 &self,
193 bucket_name: &str,
194 ttl: Option<Duration>,
196 ) -> Result<Box<dyn Bucket>, StoreError> {
197 use KeyValueStoreEnum::*;
198 Ok(match self {
199 Memory(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
200 Nats(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
201 Etcd(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
202 File(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
203 })
204 }
205
206 async fn get_bucket(&self, bucket_name: &str) -> Result<Option<Box<dyn Bucket>>, StoreError> {
207 use KeyValueStoreEnum::*;
208 let maybe_bucket: Option<Box<dyn Bucket>> = match self {
209 Memory(x) => x
210 .get_bucket(bucket_name)
211 .await?
212 .map(|b| Box::new(b) as Box<dyn Bucket>),
213 Nats(x) => x
214 .get_bucket(bucket_name)
215 .await?
216 .map(|b| Box::new(b) as Box<dyn Bucket>),
217 Etcd(x) => x
218 .get_bucket(bucket_name)
219 .await?
220 .map(|b| Box::new(b) as Box<dyn Bucket>),
221 File(x) => x
222 .get_bucket(bucket_name)
223 .await?
224 .map(|b| Box::new(b) as Box<dyn Bucket>),
225 };
226 Ok(maybe_bucket)
227 }
228
229 fn connection_id(&self) -> u64 {
230 use KeyValueStoreEnum::*;
231 match self {
232 Memory(x) => x.connection_id(),
233 Etcd(x) => x.connection_id(),
234 Nats(x) => x.connection_id(),
235 File(x) => x.connection_id(),
236 }
237 }
238
239 fn shutdown(&self) {
240 use KeyValueStoreEnum::*;
241 match self {
242 Memory(x) => x.shutdown(),
243 Etcd(x) => x.shutdown(),
244 Nats(x) => x.shutdown(),
245 File(x) => x.shutdown(),
246 }
247 }
248}
249
250#[derive(Clone)]
251pub struct Manager(Arc<KeyValueStoreEnum>);
252
253impl Default for Manager {
254 fn default() -> Self {
255 Manager::memory()
256 }
257}
258
259impl Manager {
260 pub fn memory() -> Self {
262 Self::new(KeyValueStoreEnum::Memory(MemoryStore::new()))
263 }
264
265 pub fn etcd(etcd_client: crate::transports::etcd::Client) -> Self {
266 Self::new(KeyValueStoreEnum::Etcd(EtcdStore::new(etcd_client)))
267 }
268
269 pub fn file<P: Into<PathBuf>>(cancel_token: CancellationToken, root: P) -> Self {
270 Self::new(KeyValueStoreEnum::File(FileStore::new(cancel_token, root)))
271 }
272
273 fn new(s: KeyValueStoreEnum) -> Manager {
274 Manager(Arc::new(s))
275 }
276
277 pub async fn get_or_create_bucket(
278 &self,
279 bucket_name: &str,
280 ttl: Option<Duration>,
282 ) -> Result<Box<dyn Bucket>, StoreError> {
283 self.0.get_or_create_bucket(bucket_name, ttl).await
284 }
285
286 pub async fn get_bucket(
287 &self,
288 bucket_name: &str,
289 ) -> Result<Option<Box<dyn Bucket>>, StoreError> {
290 self.0.get_bucket(bucket_name).await
291 }
292
293 pub fn connection_id(&self) -> u64 {
294 self.0.connection_id()
295 }
296
297 pub async fn load<T: for<'a> Deserialize<'a>>(
298 &self,
299 bucket: &str,
300 key: &Key,
301 ) -> Result<Option<T>, StoreError> {
302 let Some(bucket) = self.0.get_bucket(bucket).await? else {
303 return Ok(None);
305 };
306 Ok(match bucket.get(key).await? {
307 Some(card_bytes) => {
308 let card: T = serde_json::from_slice(card_bytes.as_ref())?;
309 Some(card)
310 }
311 None => None,
312 })
313 }
314
315 pub fn watch(
319 self: Arc<Self>,
320 bucket_name: &str,
321 bucket_ttl: Option<Duration>,
322 cancel_token: CancellationToken,
323 ) -> (
324 tokio::task::JoinHandle<Result<(), StoreError>>,
325 tokio::sync::mpsc::Receiver<WatchEvent>,
326 ) {
327 let bucket_name = bucket_name.to_string();
328 let (tx, rx) = tokio::sync::mpsc::channel(16384);
332 let watch_task = tokio::spawn(async move {
333 let bucket = self
335 .0
336 .get_or_create_bucket(&bucket_name, bucket_ttl)
337 .await?;
338 let mut stream = bucket.watch().await?;
339
340 for (key, bytes) in bucket.entries().await? {
342 if let Err(err) = tx
343 .send_timeout(
344 WatchEvent::Put(KeyValue::new(key, bytes)),
345 WATCH_SEND_TIMEOUT,
346 )
347 .await
348 {
349 tracing::error!(bucket_name, %err, "KeyValueStoreManager.watch failed adding existing key to channel");
350 }
351 }
352
353 loop {
355 let event = tokio::select! {
356 _ = cancel_token.cancelled() => break,
357 result = stream.next() => match result {
358 Some(event) => event,
359 None => break,
360 }
361 };
362 match event {
363 WatchEvent::Resync(_) => {
364 tokio::select! {
368 _ = cancel_token.cancelled() => break,
369 result = tx.send(event) => {
370 if let Err(err) = result {
371 tracing::error!(bucket_name, %err, "KeyValueStoreManager.watch failed adding resync to channel");
372 break;
373 }
374 }
375 }
376 }
377 event => {
378 if let Err(err) = tx.send_timeout(event, WATCH_SEND_TIMEOUT).await {
379 tracing::error!(bucket_name, %err, "KeyValueStoreManager.watch failed adding new key to channel");
380 }
381 }
382 }
383 }
384
385 Ok::<(), StoreError>(())
386 });
387 (watch_task, rx)
388 }
389
390 pub async fn publish<T: Serialize + Versioned + Send + Sync>(
391 &self,
392 bucket_name: &str,
393 bucket_ttl: Option<Duration>,
394 key: &Key,
395 obj: &mut T,
396 ) -> anyhow::Result<StoreOutcome> {
397 let obj_json = serde_json::to_vec(obj)?;
398 let bucket = self.0.get_or_create_bucket(bucket_name, bucket_ttl).await?;
399
400 let outcome = bucket.insert(key, obj_json.into(), obj.revision()).await?;
401
402 match outcome {
403 StoreOutcome::Created(revision) | StoreOutcome::Exists(revision) => {
404 obj.set_revision(revision);
405 }
406 }
407 Ok(outcome)
408 }
409
410 pub fn shutdown(&self) {
413 self.0.shutdown()
414 }
415}
416
417#[async_trait]
419pub trait Bucket: Send + Sync {
420 async fn insert(
424 &self,
425 key: &Key,
426 value: bytes::Bytes,
427 revision: u64,
428 ) -> Result<StoreOutcome, StoreError>;
429
430 async fn get(&self, key: &Key) -> Result<Option<bytes::Bytes>, StoreError>;
433
434 async fn delete(&self, key: &Key) -> Result<(), StoreError>;
437
438 async fn watch(
442 &self,
443 ) -> Result<Pin<Box<dyn futures::Stream<Item = WatchEvent> + Send + '_>>, StoreError>;
444
445 async fn entries(&self) -> Result<HashMap<Key, bytes::Bytes>, StoreError>;
449}
450
451#[derive(Debug, Copy, Clone, Eq, PartialEq)]
452pub enum StoreOutcome {
453 Created(u64),
456 Exists(u64),
458}
459impl fmt::Display for StoreOutcome {
460 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461 match self {
462 StoreOutcome::Created(revision) => write!(f, "Created at {revision}"),
463 StoreOutcome::Exists(revision) => write!(f, "Exists at {revision}"),
464 }
465 }
466}
467
468#[derive(thiserror::Error, Debug)]
469pub enum StoreError {
470 #[error("Could not find bucket '{0}'")]
471 MissingBucket(String),
472
473 #[error("Could not find key '{0}'")]
474 MissingKey(String),
475
476 #[error("Internal storage error: '{0}'")]
477 ProviderError(String),
478
479 #[error("Internal NATS error: {0}")]
480 NATSError(String),
481
482 #[error("Internal etcd error: {0}")]
483 EtcdError(String),
484
485 #[error("Internal filesystem error: {0}")]
486 FilesystemError(String),
487
488 #[error("Key Value Error: {0} for bucket '{1}'")]
489 KeyValueError(String, String),
490
491 #[error("Error decoding bytes: {0}")]
492 JSONDecodeError(#[from] serde_json::error::Error),
493
494 #[error("Race condition, retry the call")]
495 Retry,
496}
497
498pub trait Versioned {
501 fn revision(&self) -> u64;
502 fn set_revision(&mut self, r: u64);
503}
504
505#[cfg(test)]
506mod tests {
507 use std::sync::Arc;
508
509 use super::*;
510 use futures::{StreamExt, pin_mut};
511
512 const BUCKET_NAME: &str = "v1/mdc";
513
514 #[allow(dead_code)]
517 pub struct TappableStream {
518 tx: tokio::sync::broadcast::Sender<WatchEvent>,
519 }
520
521 #[allow(dead_code)]
522 impl TappableStream {
523 async fn new<T>(stream: T, max_size: usize) -> Self
524 where
525 T: futures::Stream<Item = WatchEvent> + Send + 'static,
526 {
527 let (tx, _) = tokio::sync::broadcast::channel(max_size);
528 let tx2 = tx.clone();
529 tokio::spawn(async move {
530 pin_mut!(stream);
531 while let Some(x) = stream.next().await {
532 let _ = tx2.send(x);
533 }
534 });
535 TappableStream { tx }
536 }
537
538 fn subscribe(&self) -> tokio::sync::broadcast::Receiver<WatchEvent> {
539 self.tx.subscribe()
540 }
541 }
542
543 fn init() {
544 crate::logging::init();
545 }
546
547 #[tokio::test]
548 async fn test_memory_storage() -> anyhow::Result<()> {
549 init();
550
551 let s = Arc::new(MemoryStore::new());
552 let s2 = Arc::clone(&s);
553
554 let bucket = s.get_or_create_bucket(BUCKET_NAME, None).await?;
555 let res = bucket.insert(&"test1".into(), "value1".into(), 0).await?;
556 assert_eq!(res, StoreOutcome::Created(0));
557
558 let expected = [
559 WatchEvent::Put(KeyValue::new(Key::new("test1".into()), "value1".into())),
560 WatchEvent::Put(KeyValue::new(Key::new("test2".into()), "value2".into())),
561 WatchEvent::Put(KeyValue::new(
562 Key::new("test2".into()),
563 "value2-updated".into(),
564 )),
565 WatchEvent::Put(KeyValue::new(Key::new("test3".into()), "value3".into())),
566 ];
567
568 let (got_first_tx, got_first_rx) = tokio::sync::oneshot::channel();
569 let ingress = tokio::spawn(async move {
570 let b2 = s2.get_or_create_bucket(BUCKET_NAME, None).await?;
571 let mut stream = b2.watch().await?;
572
573 let v = stream.next().await.unwrap();
575 assert_eq!(v, expected[0]);
576
577 got_first_tx.send(()).unwrap();
578
579 let v = stream.next().await.unwrap();
581 assert_eq!(v, expected[1]);
582
583 let v = stream.next().await.unwrap();
584 assert_eq!(v, expected[2]);
585
586 let v = stream.next().await.unwrap();
587 assert_eq!(v, expected[3]);
588
589 Ok::<_, StoreError>(())
590 });
591
592 got_first_rx.await?;
596
597 let res = bucket.insert(&"test2".into(), "value2".into(), 0).await?;
598 assert_eq!(res, StoreOutcome::Created(0));
599
600 let res = bucket.insert(&"test2".into(), "value2".into(), 0).await?;
602 assert_eq!(res, StoreOutcome::Exists(0));
603
604 let res = bucket
606 .insert(&"test2".into(), "value2-updated".into(), 1)
607 .await?;
608 assert_eq!(res, StoreOutcome::Created(1));
609
610 let res = bucket.insert(&"test3".into(), "value3".into(), 0).await?;
611 assert_eq!(res, StoreOutcome::Created(0));
612
613 let _ = ingress.await?;
615
616 Ok(())
617 }
618
619 #[tokio::test]
620 async fn test_broadcast_stream() -> anyhow::Result<()> {
621 init();
622
623 let s: &'static _ = Box::leak(Box::new(MemoryStore::new()));
624 let bucket: &'static _ =
625 Box::leak(Box::new(s.get_or_create_bucket(BUCKET_NAME, None).await?));
626
627 let res = bucket.insert(&"test1".into(), "value1".into(), 0).await?;
628 assert_eq!(res, StoreOutcome::Created(0));
629
630 let stream = bucket.watch().await?;
631 let tap = TappableStream::new(stream, 10).await;
632
633 let mut rx1 = tap.subscribe();
634 let mut rx2 = tap.subscribe();
635
636 let item = WatchEvent::Put(KeyValue::new(Key::new("test1".to_string()), "GK".into()));
637 let item_clone = item.clone();
638 let handle1 = tokio::spawn(async move {
639 let b = rx1.recv().await.unwrap();
640 assert_eq!(b, item_clone);
641 });
642 let handle2 = tokio::spawn(async move {
643 let b = rx2.recv().await.unwrap();
644 assert_eq!(b, item);
645 });
646
647 bucket.insert(&"test1".into(), "GK".into(), 1).await?;
648
649 let _ = futures::join!(handle1, handle2);
650 Ok(())
651 }
652}