pub trait StorageBackend<K, V>where
K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,{
// Required methods
fn load_all<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<HashMap<K, V>, PersistentError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn save<'life0, 'async_trait>(
&'life0 self,
key: K,
value: V,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
// Provided methods
fn flush<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>
where Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
fn contains_key<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>
where Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn len<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<usize, PersistentError>> + Send + 'async_trait>>
where Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
fn is_empty<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>
where Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
}
Expand description
A trait for implementing storage backends for PersistentMap
.
This trait defines the interface that all storage backends must implement. It provides methods for loading, saving, and deleting key-value pairs.
§Type Parameters
K
: The key type, which must be hashable, serializable, and cloneableV
: The value type, which must be serializable and cloneable
§Examples
Implementing a custom backend:
The StorageBackend
trait defines the interface for persistent storage backends.
By implementing this trait, you can create custom storage solutions for the PersistentMap
.
§Implementing a Custom Backend
To implement a custom backend, you need to:
- Create a struct to hold your backend-specific data
- Implement the required methods:
load_all
,save
, anddelete
- Optionally override the
flush
method if your backend buffers writes
§Example Implementation
Here’s an example of a custom backend that stores data in a JSON file:
use persistent_map::{StorageBackend, PersistentError, Result};
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;
use serde::{Serialize, de::DeserializeOwned};
use std::hash::Hash;
struct JsonFileBackend {
path: PathBuf,
}
impl JsonFileBackend {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
// Helper method to ensure the file exists
fn ensure_file_exists(&self) -> std::io::Result<()> {
if !self.path.exists() {
// Create parent directories if they don't exist
if let Some(parent) = self.path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
// Create the file with an empty JSON object
fs::write(&self.path, "{}")?;
}
Ok(())
}
}
#[async_trait::async_trait]
impl<K, V> StorageBackend<K, V> for JsonFileBackend
where
K: Eq + Hash + Clone + Serialize + DeserializeOwned + Send + Sync + ToString + 'static,
V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
{
async fn load_all(&self) -> Result<HashMap<K, V>, PersistentError> {
// Ensure the file exists
self.ensure_file_exists()?;
// If the file is empty or contains just "{}", return an empty HashMap
let content = fs::read_to_string(&self.path)?;
if content.trim() == "{}" {
return Ok(HashMap::new());
}
// Parse the JSON file
let map = serde_json::from_str(&content)
.map_err(|e| PersistentError::Serde(e))?;
Ok(map)
}
async fn save(&self, key: K, value: V) -> Result<(), PersistentError> {
// Ensure the file exists
self.ensure_file_exists()?;
// Load existing data
let mut map: HashMap<K, V> = self.load_all().await?;
// Update the map
map.insert(key, value);
// Write back to the file
let content = serde_json::to_string_pretty(&map)
.map_err(|e| PersistentError::Serde(e))?;
fs::write(&self.path, content)?;
Ok(())
}
async fn delete(&self, key: &K) -> Result<(), PersistentError> {
// Ensure the file exists
self.ensure_file_exists()?;
// Load existing data
let mut map: HashMap<K, V> = self.load_all().await?;
// Remove the key
map.remove(key);
// Write back to the file
let content = serde_json::to_string_pretty(&map)
.map_err(|e| PersistentError::Serde(e))?;
fs::write(&self.path, content)?;
Ok(())
}
async fn flush(&self) -> Result<(), PersistentError> {
// No buffering in this implementation, so nothing to flush
Ok(())
}
}
§Best Practices for Custom Backends
- Error Handling: Convert backend-specific errors to
PersistentError
- Concurrency: Ensure your backend is safe for concurrent access
- Performance: Consider caching or batching operations for better performance
- Resilience: Handle edge cases like missing files or corrupted data gracefully
- Testing: Create tests that verify persistence across application restarts
Required Methods§
Sourcefn load_all<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<HashMap<K, V>, PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn load_all<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<HashMap<K, V>, PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Load all key-value pairs from the storage backend.
This method is called when initializing a PersistentMap
to populate
the in-memory map with existing data.
§Errors
Returns a PersistentError
if loading fails for any reason, such as:
- The storage location doesn’t exist
- The data is corrupted or in an invalid format
- There are permission issues
§Implementation Notes
- This method should be idempotent and safe to call multiple times
- If the storage is empty or doesn’t exist yet, return an empty
HashMap
- Consider adding error recovery mechanisms for corrupted data
Sourcefn save<'life0, 'async_trait>(
&'life0 self,
key: K,
value: V,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn save<'life0, 'async_trait>(
&'life0 self,
key: K,
value: V,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Save a key-value pair to the storage backend.
This method is called whenever a key-value pair is inserted into the map.
§Errors
Returns a PersistentError
if saving fails for any reason, such as:
- The storage location is not writable
- There are permission issues
- The backend has reached capacity
§Implementation Notes
- This method should be atomic if possible
- Consider batching or caching writes for better performance
- If your backend requires serialization, handle serialization errors appropriately
Sourcefn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Delete a key-value pair from the storage backend.
This method is called whenever a key-value pair is removed from the map.
§Errors
Returns a PersistentError
if deletion fails for any reason, such as:
- The storage location is not writable
- There are permission issues
§Implementation Notes
- This method should be idempotent - deleting a non-existent key should not be an error
- Consider optimizing for the case where the key doesn’t exist
Provided Methods§
Sourcefn flush<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn flush<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
Flush any buffered writes to the storage backend.
This method is called when the user explicitly requests to ensure all data is persisted.
§Errors
Returns a PersistentError
if flushing fails for any reason, such as:
- The storage location is not writable
- There are permission issues
§Implementation Notes
- This method is optional and has a default implementation that does nothing
- Backends that buffer writes should override this method to ensure data is persisted
- This method should be idempotent and safe to call multiple times
Sourcefn contains_key<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn contains_key<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 K,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Check if a key exists in the storage backend.
This is an optional method with a default implementation that loads all data and checks if the key exists. Backend implementations can override this for better performance if they can check for existence without loading all data.
§Errors
Returns a PersistentError
if the check fails for any reason.
§Implementation Notes
- The default implementation is inefficient for large datasets
- Override this method if your backend can check for existence more efficiently
Sourcefn len<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<usize, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn len<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<usize, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
Get the number of key-value pairs in the storage backend.
This is an optional method with a default implementation that loads all data and counts the entries. Backend implementations can override this for better performance if they can count entries without loading all data.
§Errors
Returns a PersistentError
if the count operation fails for any reason.
§Implementation Notes
- The default implementation is inefficient for large datasets
- Override this method if your backend can count entries more efficiently
Sourcefn is_empty<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn is_empty<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<bool, PersistentError>> + Send + 'async_trait>>where
Self: Sync + 'async_trait,
'life0: 'async_trait,
Check if the storage backend is empty.
This is an optional method with a default implementation that uses len()
.
Backend implementations can override this for better performance.
§Errors
Returns a PersistentError
if the check fails for any reason.
§Implementation Notes
- The default implementation uses
len()
, which may be inefficient - Override this method if your backend can check emptiness more efficiently
Implementors§
impl<K, V> StorageBackend<K, V> for InMemoryBackend
impl<K, V> StorageBackend<K, V> for SqliteBackend
Implementation of the StorageBackend
trait for SqliteBackend
.
This implementation provides methods for loading, saving, and deleting key-value pairs from a SQLite database.