persistent_map/
lib.rs

1//! # `PersistentMap`
2//!
3//! `persistent-map` provides an in-memory key-value store with async, pluggable persistence.
4//!
5//! It combines the speed of [`DashMap`](https://docs.rs/dashmap) for in-memory operations
6//! with the durability of various storage backends for persistence.
7//!
8//! ## Features
9//!
10//! - **Fast in-memory access**: Uses `DashMap` for concurrent read/write operations
11//! - **Async API**: Non-blocking I/O operations for persistence
12//! - **Multiple backends**: `SQLite`, `CSV`, in-memory, and extensible for more
13//! - **Type-safe**: Works with any types that implement the required traits
14//!
15//! ## Example
16//!
17//! ```rust,no_run
18//! use persistent_map::{PersistentMap, Result};
19//! # #[cfg(feature = "sqlite")]
20//! use persistent_map::sqlite::SqliteBackend;
21//!
22//! # #[cfg(feature = "sqlite")]
23//! # async fn example() -> Result<()> {
24//! # // Create a SQLite backend
25//! # let backend = SqliteBackend::new("my_database.db").await?;
26//! #
27//! # // Initialize the persistent map
28//! # let map = PersistentMap::new(backend).await?;
29//! #
30//! # // Insert a key-value pair (persists automatically)
31//! # map.insert("hello".to_string(), "world".to_string()).await?;
32//! #
33//! # // Retrieve a value (from memory)
34//! # assert_eq!(map.get(&"hello".to_string()), Some("world".to_string()));
35//! # Ok(())
36//! # }
37//! #
38//! # #[cfg(not(feature = "sqlite"))]
39//! # fn example() {}
40//! ```
41
42use dashmap::DashMap;
43use serde::{de::DeserializeOwned, Serialize};
44use std::{collections::HashMap, hash::Hash};
45use thiserror::Error;
46/// A trait for implementing storage backends for `PersistentMap`.
47///
48/// This trait defines the interface that all storage backends must implement.
49/// It provides methods for loading, saving, and deleting key-value pairs.
50///
51/// # Type Parameters
52///
53/// * `K`: The key type, which must be hashable, serializable, and cloneable
54/// * `V`: The value type, which must be serializable and cloneable
55///
56/// # Examples
57///
58/// Implementing a custom backend:
59///
60/// The `StorageBackend` trait defines the interface for persistent storage backends.
61/// By implementing this trait, you can create custom storage solutions for the `PersistentMap`.
62///
63/// # Implementing a Custom Backend
64///
65/// To implement a custom backend, you need to:
66///
67/// 1. Create a struct to hold your backend-specific data
68/// 2. Implement the required methods: `load_all`, `save`, and `delete`
69/// 3. Optionally override the `flush` method if your backend buffers writes
70///
71/// # Example Implementation
72///
73/// Here's an example of a custom backend that stores data in a JSON file:
74///
75/// ```rust
76/// use persistent_map::{StorageBackend, PersistentError, Result};
77/// use std::collections::HashMap;
78/// use std::path::PathBuf;
79/// use std::fs;
80/// use serde::{Serialize, de::DeserializeOwned};
81/// use std::hash::Hash;
82///
83/// struct JsonFileBackend {
84///     path: PathBuf,
85/// }
86///
87/// impl JsonFileBackend {
88///     pub fn new(path: impl Into<PathBuf>) -> Self {
89///         Self { path: path.into() }
90///     }
91///
92///     // Helper method to ensure the file exists
93///     fn ensure_file_exists(&self) -> std::io::Result<()> {
94///         if !self.path.exists() {
95///             // Create parent directories if they don't exist
96///             if let Some(parent) = self.path.parent() {
97///                 if !parent.exists() {
98///                     fs::create_dir_all(parent)?;
99///                 }
100///             }
101///
102///             // Create the file with an empty JSON object
103///             fs::write(&self.path, "{}")?;
104///         }
105///         Ok(())
106///     }
107/// }
108///
109/// #[async_trait::async_trait]
110/// impl<K, V> StorageBackend<K, V> for JsonFileBackend
111/// where
112///     K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + ToString + 'static,
113///     V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
114/// {
115///     async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError> {
116///         // Ensure the file exists
117///         self.ensure_file_exists()?;
118///
119///         // If the file is empty or contains just "{}", return an empty HashMap
120///         let content = fs::read_to_string(&self.path)?;
121///         if content.trim() == "{}" {
122///             return Ok(HashMap::new());
123///         }
124///
125///         // Parse the JSON file
126///         let map = serde_json::from_str(&content)
127///             .map_err(|e| PersistentError::Serde(e))?;
128///
129///         Ok(map)
130///     }
131///
132///     async fn save(&self, key: K, value: V) -> Result<(), PersistentError> {
133///         // Ensure the file exists
134///         self.ensure_file_exists()?;
135///
136///         // Load existing data
137///         let mut map: HashMap<K, V> = self.load_all().await?;
138///
139///         // Update the map
140///         map.insert(key, value);
141///
142///         // Write back to the file
143///         let content = serde_json::to_string_pretty(&map)
144///             .map_err(|e| PersistentError::Serde(e))?;
145///
146///         fs::write(&self.path, content)?;
147///
148///         Ok(())
149///     }
150///
151///     async fn delete(&self, key: &K) -> Result<(), PersistentError> {
152///         // Ensure the file exists
153///         self.ensure_file_exists()?;
154///
155///         // Load existing data
156///         let mut map: HashMap<K, V> = self.load_all().await?;
157///
158///         // Remove the key
159///         map.remove(key);
160///
161///         // Write back to the file
162///         let content = serde_json::to_string_pretty(&map)
163///             .map_err(|e| PersistentError::Serde(e))?;
164///
165///         fs::write(&self.path, content)?;
166///
167///         Ok(())
168///     }
169///
170///     async fn flush(&self) -> Result<(), PersistentError> {
171///         // No buffering in this implementation, so nothing to flush
172///         Ok(())
173///     }
174/// }
175/// ```
176///
177/// # Best Practices for Custom Backends
178///
179/// 1. **Error Handling**: Convert backend-specific errors to `PersistentError`
180/// 2. **Concurrency**: Ensure your backend is safe for concurrent access
181/// 3. **Performance**: Consider caching or batching operations for better performance
182/// 4. **Resilience**: Handle edge cases like missing files or corrupted data gracefully
183/// 5. **Testing**: Create tests that verify persistence across application restarts
184#[async_trait::async_trait]
185pub trait StorageBackend<K, V>
186where
187    K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
188    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
189{
190    /// Load all key-value pairs from the storage backend.
191    ///
192    /// This method is called when initializing a `PersistentMap` to populate
193    /// the in-memory map with existing data.
194    ///
195    /// # Errors
196    ///
197    /// Returns a `PersistentError` if loading fails for any reason, such as:
198    /// - The storage location doesn't exist
199    /// - The data is corrupted or in an invalid format
200    /// - There are permission issues
201    ///
202    /// # Implementation Notes
203    ///
204    /// - This method should be idempotent and safe to call multiple times
205    /// - If the storage is empty or doesn't exist yet, return an empty `HashMap`
206    /// - Consider adding error recovery mechanisms for corrupted data
207    async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError>;
208
209    /// Save a key-value pair to the storage backend.
210    ///
211    /// This method is called whenever a key-value pair is inserted into the map.
212    ///
213    /// # Errors
214    ///
215    /// Returns a `PersistentError` if saving fails for any reason, such as:
216    /// - The storage location is not writable
217    /// - There are permission issues
218    /// - The backend has reached capacity
219    ///
220    /// # Implementation Notes
221    ///
222    /// - This method should be atomic if possible
223    /// - Consider batching or caching writes for better performance
224    /// - If your backend requires serialization, handle serialization errors appropriately
225    async fn save(&self, key: K, value: V) -> Result<(), PersistentError>;
226
227    /// Delete a key-value pair from the storage backend.
228    ///
229    /// This method is called whenever a key-value pair is removed from the map.
230    ///
231    /// # Errors
232    ///
233    /// Returns a `PersistentError` if deletion fails for any reason, such as:
234    /// - The storage location is not writable
235    /// - There are permission issues
236    ///
237    /// # Implementation Notes
238    ///
239    /// - This method should be idempotent - deleting a non-existent key should not be an error
240    /// - Consider optimizing for the case where the key doesn't exist
241    async fn delete(&self, key: &K) -> Result<(), PersistentError>;
242
243    /// Flush any buffered writes to the storage backend.
244    ///
245    /// This method is called when the user explicitly requests to ensure all data is persisted.
246    ///
247    /// # Errors
248    ///
249    /// Returns a `PersistentError` if flushing fails for any reason, such as:
250    /// - The storage location is not writable
251    /// - There are permission issues
252    ///
253    /// # Implementation Notes
254    ///
255    /// - This method is optional and has a default implementation that does nothing
256    /// - Backends that buffer writes should override this method to ensure data is persisted
257    /// - This method should be idempotent and safe to call multiple times
258    async fn flush(&self) -> Result<(), PersistentError> {
259        Ok(())
260    }
261
262    /// Check if a key exists in the storage backend.
263    ///
264    /// This is an optional method with a default implementation that loads all data
265    /// and checks if the key exists. Backend implementations can override this
266    /// for better performance if they can check for existence without loading all data.
267    ///
268    /// # Errors
269    ///
270    /// Returns a `PersistentError` if the check fails for any reason.
271    ///
272    /// # Implementation Notes
273    ///
274    /// - The default implementation is inefficient for large datasets
275    /// - Override this method if your backend can check for existence more efficiently
276    async fn contains_key(&self, key: &K) -> Result<bool, PersistentError> {
277        let all = self.load_all().await?;
278        Ok(all.contains_key(key))
279    }
280
281    /// Get the number of key-value pairs in the storage backend.
282    ///
283    /// This is an optional method with a default implementation that loads all data
284    /// and counts the entries. Backend implementations can override this
285    /// for better performance if they can count entries without loading all data.
286    ///
287    /// # Errors
288    ///
289    /// Returns a `PersistentError` if the count operation fails for any reason.
290    ///
291    /// # Implementation Notes
292    ///
293    /// - The default implementation is inefficient for large datasets
294    /// - Override this method if your backend can count entries more efficiently
295    async fn len(&self) -> Result<usize, PersistentError> {
296        let all = self.load_all().await?;
297        Ok(all.len())
298    }
299
300    /// Check if the storage backend is empty.
301    ///
302    /// This is an optional method with a default implementation that uses `len()`.
303    /// Backend implementations can override this for better performance.
304    ///
305    /// # Errors
306    ///
307    /// Returns a `PersistentError` if the check fails for any reason.
308    ///
309    /// # Implementation Notes
310    ///
311    /// - The default implementation uses `len()`, which may be inefficient
312    /// - Override this method if your backend can check emptiness more efficiently
313    async fn is_empty(&self) -> Result<bool, PersistentError> {
314        Ok(self.len().await? == 0)
315    }
316}
317
318/// Errors that can occur when using `PersistentMap`.
319///
320/// This enum represents all the possible errors that can occur when using
321/// the various storage backends.
322#[derive(Error, Debug)]
323pub enum PersistentError {
324    /// An error occurred in the `SQLite` backend.
325    #[cfg(feature = "sqlite")]
326    #[error("sqlite error: {0}")]
327    Sqlite(#[from] tokio_rusqlite::Error),
328
329    /// An error occurred in the CSV backend.
330    #[cfg(feature = "csv_backend")]
331    #[error("csv error: {0}")]
332    Csv(String),
333
334    /// An I/O error occurred.
335    #[error("io error: {0}")]
336    Io(#[from] std::io::Error),
337
338    /// A serialization or deserialization error occurred.
339    #[error("serde error: {0}")]
340    Serde(#[from] serde_json::Error),
341
342    /// An error occurred in the Sled backend.
343    #[cfg(feature = "sled_backend")]
344    #[error("sled error: {0}")]
345    Sled(#[from] sled::Error),
346}
347
348/// Shorthand Result with error defaulting to `PersistentError`.
349pub type Result<T, E = PersistentError> = std::result::Result<T, E>;
350
351// Re-export backends
352#[cfg(feature = "csv_backend")]
353pub use crate::backends::csv;
354
355#[cfg(feature = "in_memory")]
356pub use crate::backends::in_memory;
357
358#[cfg(feature = "sqlite")]
359pub use crate::backends::sqlite;
360
361mod backends;
362
363/// A persistent key-value map with in-memory caching.
364///
365/// `PersistentMap` combines a fast in-memory `DashMap` with a persistent
366/// storage backend. It provides a simple API for storing and retrieving
367/// key-value pairs, with automatic persistence.
368///
369/// # Type Parameters
370///
371/// * `K`: The key type, which must be hashable, serializable, and cloneable
372/// * `V`: The value type, which must be serializable and cloneable
373/// * `B`: The storage backend type, which must implement `StorageBackend<K, V>`
374///
375/// # Examples
376///
377/// ```rust,no_run
378/// use persistent_map::{PersistentMap, Result};
379/// # #[cfg(feature = "sqlite")]
380/// use persistent_map::sqlite::SqliteBackend;
381///
382/// # #[cfg(feature = "sqlite")]
383/// # async fn example() -> Result<()> {
384/// # // Create a SQLite backend
385/// # let backend = SqliteBackend::new("my_database.db").await?;
386/// #
387/// # // Initialize the persistent map
388/// # let map = PersistentMap::new(backend).await?;
389/// #
390/// # // Insert a key-value pair (persists automatically)
391/// # map.insert("hello".to_string(), "world".to_string()).await?;
392/// #
393/// # // Retrieve a value (from memory)
394/// # assert_eq!(map.get(&"hello".to_string()), Some("world".to_string()));
395/// #
396/// # // Remove a key-value pair (removes from persistence too)
397/// # let old_value = map.remove(&"hello".to_string()).await?;
398/// # assert_eq!(old_value, Some("world".to_string()));
399/// # Ok(())
400/// # }
401/// #
402/// # #[cfg(not(feature = "sqlite"))]
403/// # fn example() {}
404/// ```
405pub struct PersistentMap<K, V, B>
406where
407    K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
408    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
409    B: StorageBackend<K, V> + Send + Sync + 'static,
410{
411    /// The in-memory map for fast access
412    map: DashMap<K, V>,
413
414    /// The storage backend for persistence
415    backend: B,
416}
417
418impl<K, V, B> PersistentMap<K, V, B>
419where
420    K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
421    V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
422    B: StorageBackend<K, V> + Send + Sync + 'static,
423{
424    /// Creates a new `PersistentMap` with the given storage backend.
425    ///
426    /// This method initializes the map and loads all existing key-value pairs
427    /// from the storage backend into memory.
428    ///
429    /// # Examples
430    ///
431    /// ```rust,no_run
432    /// use persistent_map::{PersistentMap, Result};
433    /// # #[cfg(feature = "sqlite")]
434    /// use persistent_map::sqlite::SqliteBackend;
435    ///
436    /// # #[cfg(feature = "sqlite")]
437    /// # async fn example() -> Result<()> {
438    /// # let backend = SqliteBackend::new("my_database.db").await?;
439    /// # let map: PersistentMap<String, String, _> = PersistentMap::new(backend).await?;
440    /// # Ok(())
441    /// # }
442    /// #
443    /// # #[cfg(not(feature = "sqlite"))]
444    /// # fn example() {}
445    /// ```
446    /// # Errors
447    ///
448    /// Returns an error if loading from the backend fails.
449    #[inline]
450    pub async fn new(backend: B) -> Result<Self> {
451        let map = DashMap::new();
452        let pm = Self { map, backend };
453        pm.load().await?;
454        Ok(pm)
455    }
456
457    /// Loads all key-value pairs from the storage backend into memory.
458    ///
459    /// This method is called automatically when creating a new `PersistentMap`,
460    /// but can also be called manually to refresh the in-memory cache.
461    ///
462    /// # Examples
463    ///
464    /// ```rust,no_run
465    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
466    /// #
467    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
468    /// // Reload all data from the storage backend
469    /// map.load().await?;
470    /// # Ok(())
471    /// # }
472    /// ```
473    /// # Errors
474    ///
475    /// Returns an error if loading from the backend fails.
476    #[inline]
477    pub async fn load(&self) -> Result<(), PersistentError> {
478        let all = self.backend.load_all().await?;
479        for (k, v) in all {
480            self.map.insert(k, v);
481        }
482        Ok(())
483    }
484
485    /// Inserts a key-value pair into the map and persists it to the storage backend.
486    ///
487    /// If the map already contains the key, the value is updated and the old value
488    /// is returned. Otherwise, `None` is returned.
489    ///
490    /// # Examples
491    ///
492    /// ```rust,no_run
493    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
494    /// #
495    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
496    /// // Insert a new key-value pair
497    /// let old = map.insert("key".to_string(), "value".to_string()).await?;
498    /// assert_eq!(old, None);
499    ///
500    /// // Update an existing key
501    /// let old = map.insert("key".to_string(), "new value".to_string()).await?;
502    /// assert_eq!(old, Some("value".to_string()));
503    /// # Ok(())
504    /// # }
505    /// ```
506    /// # Errors
507    ///
508    /// Returns an error if saving to the backend fails.
509    #[inline]
510    pub async fn insert(&self, key: K, value: V) -> Result<Option<V>> {
511        let old = self.map.insert(key.clone(), value.clone());
512        self.backend.save(key, value).await?;
513        Ok(old)
514    }
515
516    /// Retrieves a value from the map by its key.
517    ///
518    /// This method only accesses the in-memory map and does not interact with
519    /// the storage backend, making it very fast.
520    ///
521    /// # Examples
522    ///
523    /// ```rust,no_run
524    /// # use persistent_map::{PersistentMap, StorageBackend};
525    /// #
526    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
527    /// // Get a value
528    /// if let Some(value) = map.get(&"key".to_string()) {
529    ///     println!("Value: {}", value);
530    /// }
531    /// # }
532    /// ```
533    #[inline]
534    pub fn get(&self, key: &K) -> Option<V> {
535        self.map.get(key).map(|r| r.value().clone())
536    }
537
538    /// Removes a key-value pair from the map and the storage backend.
539    ///
540    /// If the map contains the key, the key-value pair is removed and the old value
541    /// is returned. Otherwise, `None` is returned.
542    ///
543    /// # Examples
544    ///
545    /// ```rust,no_run
546    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
547    /// #
548    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
549    /// // Remove a key-value pair
550    /// let old = map.remove(&"key".to_string()).await?;
551    /// if let Some(value) = old {
552    ///     println!("Removed value: {}", value);
553    /// }
554    /// # Ok(())
555    /// # }
556    /// ```
557    /// # Errors
558    ///
559    /// Returns an error if deleting from the backend fails.
560    #[inline]
561    pub async fn remove(&self, key: &K) -> Result<Option<V>> {
562        let old = self.map.remove(key).map(|(_, v)| v);
563        if old.is_some() {
564            match self.backend.delete(key).await {
565                Ok(()) => {}
566                Err(e) => return Err(e),
567            }
568        }
569        Ok(old)
570    }
571
572    /// Returns the number of key-value pairs in the map.
573    ///
574    /// # Examples
575    ///
576    /// ```rust,no_run
577    /// # use persistent_map::{PersistentMap, StorageBackend};
578    /// #
579    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
580    /// let count = map.len();
581    /// println!("Map contains {} entries", count);
582    /// # }
583    /// ```
584    #[inline]
585    pub fn len(&self) -> usize {
586        self.map.len()
587    }
588
589    /// Returns `true` if the map contains no key-value pairs.
590    ///
591    /// # Examples
592    ///
593    /// ```rust,no_run
594    /// # use persistent_map::{PersistentMap, StorageBackend};
595    /// #
596    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
597    /// if map.is_empty() {
598    ///     println!("Map is empty");
599    /// }
600    /// # }
601    /// ```
602    #[inline]
603    pub fn is_empty(&self) -> bool {
604        self.map.is_empty()
605    }
606
607    /// Returns `true` if the map contains the specified key.
608    ///
609    /// # Examples
610    ///
611    /// ```rust,no_run
612    /// # use persistent_map::{PersistentMap, StorageBackend};
613    /// #
614    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
615    /// if map.contains_key(&"key".to_string()) {
616    ///     println!("Map contains the key");
617    /// }
618    /// # }
619    /// ```
620    #[inline]
621    pub fn contains_key(&self, key: &K) -> bool {
622        self.map.contains_key(key)
623    }
624
625    /// Clears the in-memory map without affecting the storage backend.
626    ///
627    /// This method only clears the in-memory cache and does not delete any data
628    /// from the storage backend. To completely clear the storage, you should
629    /// delete the underlying storage file or database.
630    ///
631    /// # Examples
632    ///
633    /// ```rust,no_run
634    /// # use persistent_map::{PersistentMap, StorageBackend};
635    /// #
636    /// # fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) {
637    /// // Clear the in-memory cache
638    /// map.clear();
639    /// assert_eq!(map.len(), 0);
640    /// # }
641    /// ```
642    #[inline]
643    pub fn clear(&self) {
644        self.map.clear();
645    }
646
647    /// Flushes any buffered writes to the storage backend.
648    ///
649    /// This method is useful for backends that buffer writes for performance.
650    /// It ensures that all data is persisted to the storage medium.
651    ///
652    /// # Examples
653    ///
654    /// ```rust,no_run
655    /// # use persistent_map::{PersistentMap, StorageBackend, Result};
656    /// #
657    /// # async fn example(map: PersistentMap<String, String, impl StorageBackend<String, String> + Send + Sync>) -> Result<()> {
658    /// // Ensure all data is persisted
659    /// map.flush().await?;
660    /// # Ok(())
661    /// # }
662    /// ```
663    /// # Errors
664    ///
665    /// Returns an error if flushing the backend fails.
666    #[inline]
667    pub async fn flush(&self) -> Result<(), PersistentError> {
668        self.backend.flush().await
669    }
670
671    /// Returns a reference to the storage backend.
672    ///
673    /// This method is useful for accessing backend-specific functionality.
674    ///
675    /// # Examples
676    ///
677    /// ```rust,no_run
678    /// # use persistent_map::{PersistentMap, StorageBackend};
679    /// #
680    /// # fn example<B>(map: PersistentMap<String, String, B>)
681    /// # where B: StorageBackend<String, String> + Send + Sync
682    /// # {
683    /// let backend = map.backend();
684    /// // Use backend-specific functionality
685    /// # }
686    /// ```
687    #[inline]
688    pub const fn backend(&self) -> &B {
689        &self.backend
690    }
691}