Skip to main content

rustvello_mem/
client_data_store.rs

1//! In-memory [`ClientDataStore`] backend.
2
3use std::collections::HashMap;
4use tokio::sync::Mutex;
5
6use async_trait::async_trait;
7
8use rustvello_core::client_data_store::ClientDataStore;
9use rustvello_core::error::{RustvelloError, RustvelloResult};
10
11/// In-memory client data store backed by a `HashMap`.
12///
13/// All data lives in process memory and is lost on restart.
14/// Suitable for tests and development.
15pub struct MemClientDataStore {
16    data: Mutex<HashMap<String, String>>,
17}
18
19impl MemClientDataStore {
20    pub fn new() -> Self {
21        Self {
22            data: Mutex::new(HashMap::new()),
23        }
24    }
25}
26
27impl Default for MemClientDataStore {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33#[async_trait]
34impl ClientDataStore for MemClientDataStore {
35    async fn store(&self, key: &str, value: &str) -> RustvelloResult<()> {
36        self.data
37            .lock()
38            .await
39            .insert(key.to_owned(), value.to_owned());
40        Ok(())
41    }
42
43    async fn retrieve(&self, key: &str) -> RustvelloResult<String> {
44        self.data
45            .lock()
46            .await
47            .get(key)
48            .cloned()
49            .ok_or_else(|| RustvelloError::state_backend(format!("key not found: {key}")))
50    }
51
52    async fn purge(&self) -> RustvelloResult<()> {
53        self.data.lock().await.clear();
54        Ok(())
55    }
56
57    fn backend_name(&self) -> &'static str {
58        "In-Memory"
59    }
60
61    async fn usage_stats(&self) -> Vec<(&'static str, String)> {
62        let count = self.data.lock().await.len();
63        vec![("Stored Entries", count.to_string())]
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[tokio::test]
72    async fn store_and_retrieve() {
73        let store = MemClientDataStore::new();
74        store.store("k1", "v1").await.unwrap();
75        assert_eq!(store.retrieve("k1").await.unwrap(), "v1");
76    }
77
78    #[tokio::test]
79    async fn retrieve_missing_key_errors() {
80        let store = MemClientDataStore::new();
81        let err = store.retrieve("nonexistent").await;
82        assert!(err.is_err());
83    }
84
85    #[tokio::test]
86    async fn purge_removes_all() {
87        let store = MemClientDataStore::new();
88        store.store("k1", "v1").await.unwrap();
89        store.store("k2", "v2").await.unwrap();
90        store.purge().await.unwrap();
91        assert!(store.retrieve("k1").await.is_err());
92        assert!(store.retrieve("k2").await.is_err());
93    }
94
95    #[tokio::test]
96    async fn upsert_semantics() {
97        let store = MemClientDataStore::new();
98        store.store("k1", "v1").await.unwrap();
99        store.store("k1", "v2").await.unwrap();
100        assert_eq!(store.retrieve("k1").await.unwrap(), "v2");
101    }
102}