pub trait Store:
Send
+ Sync
+ Clone {
type Error: StdError + Send + Sync + 'static;
// Required methods
fn set_registry<'life0, 'async_trait>(
&'life0 self,
registry: Registry<Valid>,
ipfs_hash: String,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_registry<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Registry<Valid>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_registry_ipfs_hash<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_asset_info<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<Option<AssetInfo>, Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn insert_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_info: AssetInfo,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn insert_batch_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_infos: Vec<AssetInfo>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
The universal trait for all stores. All implementations must be thread-safe and atomic.
This trait defines the common interface that all storage implementations must provide, enabling the system to interact with different storage backends in a uniform way. Each store is responsible for reliably persisting asset information and registry data.
§Thread Safety and Atomicity
All implementations must be thread-safe (Send + Sync) and provide atomic operations to ensure data consistency when used concurrently.
§Required Methods
Implementors must define:
- Storage operations for registry data (
set_registry,get_registry,get_registry_ipfs_hash) - Asset information operations (
get_asset_info,insert_asset_info,insert_batch_asset_info)
§Example Use
Implementing a simple in-memory store:
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::sync::{Arc, RwLock};
use async_trait::async_trait;
use bothan_lib::store::Store;
use bothan_lib::registry::{Registry, Valid};
use bothan_lib::types::AssetInfo;
use thiserror::Error;
// Custom error type for the memory store
#[derive(Debug, Error)]
#[error("Memory store error: {0}")]
struct MemoryStoreError(String);
// Memory-based store implementation
#[derive(Clone)]
struct MemoryStore {
registry: Arc<RwLock<Option<(Registry<Valid>, String)>>>,
assets: Arc<RwLock<HashMap<String, HashMap<String, AssetInfo>>>>,
}
impl MemoryStore {
fn new() -> Self {
MemoryStore {
registry: Arc::new(RwLock::new(None)),
assets: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[async_trait]
impl Store for MemoryStore {
type Error = MemoryStoreError;
async fn set_registry(
&self,
registry: Registry<Valid>,
ipfs_hash: String,
) -> Result<(), Self::Error> {
let mut reg = self.registry.write().map_err(|e| MemoryStoreError(e.to_string()))?;
*reg = Some((registry, ipfs_hash));
Ok(())
}
async fn get_registry(&self) -> Result<Registry<Valid>, Self::Error> {
let reg = self.registry.read().map_err(|e| MemoryStoreError(e.to_string()))?;
match reg.as_ref() {
Some((registry, _)) => Ok(registry.clone()),
None => Err(MemoryStoreError("Registry not found".to_string())),
}
}
async fn get_registry_ipfs_hash(&self) -> Result<Option<String>, Self::Error> {
let reg = self.registry.read().map_err(|e| MemoryStoreError(e.to_string()))?;
Ok(reg.as_ref().map(|(_, hash)| hash.clone()))
}
async fn get_asset_info(
&self,
prefix: &str,
id: &str,
) -> Result<Option<AssetInfo>, Self::Error> {
let assets = self.assets.read().map_err(|e| MemoryStoreError(e.to_string()))?;
Ok(assets
.get(prefix)
.and_then(|prefix_map| prefix_map.get(id).cloned()))
}
async fn insert_asset_info(
&self,
prefix: &str,
asset_info: AssetInfo,
) -> Result<(), Self::Error> {
let mut assets = self.assets.write().map_err(|e| MemoryStoreError(e.to_string()))?;
let prefix_map = assets.entry(prefix.to_string()).or_insert_with(HashMap::new);
prefix_map.insert(asset_info.id.clone(), asset_info);
Ok(())
}
async fn insert_batch_asset_info(
&self,
prefix: &str,
asset_infos: Vec<AssetInfo>,
) -> Result<(), Self::Error> {
let mut assets = self.assets.write().map_err(|e| MemoryStoreError(e.to_string()))?;
let prefix_map = assets.entry(prefix.to_string()).or_insert_with(HashMap::new);
for asset_info in asset_infos {
prefix_map.insert(asset_info.id.clone(), asset_info);
}
Ok(())
}
}Required Associated Types§
Required Methods§
Sourcefn set_registry<'life0, 'async_trait>(
&'life0 self,
registry: Registry<Valid>,
ipfs_hash: String,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn set_registry<'life0, 'async_trait>(
&'life0 self,
registry: Registry<Valid>,
ipfs_hash: String,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Stores a validated registry and its IPFS hash in the storage backend.
This operation should be atomic to ensure registry consistency. It should replace any existing registry completely.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable or the data cannot be written.
Sourcefn get_registry<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Registry<Valid>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_registry<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Registry<Valid>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Retrieves the current validated registry from the storage backend.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable or the registry does not exist.
Sourcefn get_registry_ipfs_hash<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_registry_ipfs_hash<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Retrieves the IPFS hash of the current registry from the storage backend.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable.
Sourcefn get_asset_info<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<Option<AssetInfo>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn get_asset_info<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<Option<AssetInfo>, Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Retrieves asset information for a specific asset ID within a namespace prefix.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable.
Sourcefn insert_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_info: AssetInfo,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn insert_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_info: AssetInfo,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Stores asset information under the specified namespace prefix.
If asset information already exists for the given prefix and asset ID, it should be completely replaced with the new information.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable or the data cannot be written.
Sourcefn insert_batch_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_infos: Vec<AssetInfo>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn insert_batch_asset_info<'life0, 'life1, 'async_trait>(
&'life0 self,
prefix: &'life1 str,
asset_infos: Vec<AssetInfo>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Stores multiple asset information entries in a single batch operation.
This operation should be optimized for bulk insertion and preferably performed atomically if the backend supports transactions.
§Errors
Returns a storage-specific error if the operation fails, such as when the storage backend is unavailable, the data cannot be written, or the transaction cannot be committed.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.