Store

Trait Store 

Source
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§

Source

type Error: StdError + Send + Sync + 'static

The type returned in the event of an operation failure.

This should be a custom error type that implements the standard Error trait and captures all possible error conditions specific to the storage backend.

Required Methods§

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Implementors§