monoutils_store/
store.rs

1use std::{collections::HashSet, future::Future, pin::Pin};
2
3use bytes::Bytes;
4use libipld::Cid;
5use serde::{de::DeserializeOwned, Serialize};
6use tokio::io::{AsyncRead, AsyncReadExt};
7
8use super::{IpldReferences, SeekableReader, StoreError, StoreResult};
9
10//--------------------------------------------------------------------------------------------------
11// Types
12//--------------------------------------------------------------------------------------------------
13
14/// The different codecs supported by the IPLD store.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum Codec {
17    /// Raw bytes.
18    Raw,
19
20    /// DAG-CBOR codec.
21    DagCbor,
22
23    /// DAG-JSON codec.
24    DagJson,
25
26    /// DAG-PB codec.
27    DagPb,
28}
29
30//--------------------------------------------------------------------------------------------------
31// Traits: IpldStore, IpldStoreSeekable, IpldStoreExt
32//--------------------------------------------------------------------------------------------------
33
34/// `IpldStore` is a content-addressable store for [`IPLD` (InterPlanetary Linked Data)][ipld] that
35/// emphasizes the structured nature of the data it stores.
36///
37/// It can store raw bytes of data and structured data stored as IPLD. Stored data can be fetched
38/// by their [`CID`s (Content Identifier)][cid] which is represents the fingerprint of the data.
39///
40/// ## Important
41///
42/// It is highly recommended to implement `Clone` with inexpensive cloning semantics. This is because
43/// `IpldStore`s are usually passed around a lot and cloned to be used in different parts of the
44/// application.
45///
46/// An implementation is responsible for how it stores, encodes and chunks data into blocks.
47///
48/// [cid]: https://docs.ipfs.tech/concepts/content-addressing/
49/// [ipld]: https://ipld.io/
50///
51// TODO: Add support for deleting blocks with `derefence` method.
52// TODO: Add support for specifying hash type.
53pub trait IpldStore: Clone {
54    /// Saves an IPLD serializable object to the store and returns the `Cid` to it.
55    ///
56    /// ## Errors
57    ///
58    /// If the serialized data is too large, `StoreError::NodeBlockTooLarge` error is returned.
59    fn put_node<T>(&self, data: &T) -> impl Future<Output = StoreResult<Cid>> + Send
60    where
61        T: Serialize + IpldReferences + Sync;
62
63    /// Takes a reader of raw bytes, saves it to the store and returns the `Cid` to it.
64    ///
65    /// This method allows the store to chunk large amounts of data into smaller blocks to fit the
66    /// storage medium and it may also involve creation of merkle nodes to represent the chunks.
67    ///
68    /// ## Errors
69    ///
70    /// If the bytes are too large, `StoreError::RawBlockTooLarge` error is returned.
71    fn put_bytes<'a>(
72        &'a self,
73        reader: impl AsyncRead + Send + Sync + 'a,
74    ) -> impl Future<Output = StoreResult<Cid>> + 'a;
75
76    /// Tries to save `bytes` as a single block to the store. Unlike `put_bytes`, this method does
77    /// not chunk the data and does not create intermediate merkle nodes.
78    ///
79    /// ## Errors
80    ///
81    /// If the bytes are too large, `StoreError::RawBlockTooLarge` error is returned.
82    fn put_raw_block(
83        &self,
84        bytes: impl Into<Bytes> + Send,
85    ) -> impl Future<Output = StoreResult<Cid>> + Send;
86
87    /// Gets a type stored as an IPLD data from the store by its `Cid`.
88    ///
89    /// ## Errors
90    ///
91    /// If the block is not found, `StoreError::BlockNotFound` error is returned.
92    fn get_node<D>(&self, cid: &Cid) -> impl Future<Output = StoreResult<D>> + Send
93    where
94        D: DeserializeOwned + Send;
95
96    /// Gets a reader for the underlying bytes associated with the given `Cid`.
97    ///
98    /// ## Errors
99    ///
100    /// If the block is not found, `StoreError::BlockNotFound` error is returned.
101    fn get_bytes<'a>(
102        &'a self,
103        cid: &'a Cid,
104    ) -> impl Future<Output = StoreResult<Pin<Box<dyn AsyncRead + Send + Sync + 'a>>>> + 'a;
105
106    /// Retrieves raw bytes of a single block from the store by its `Cid`.
107    ///
108    /// Unlike `get_stream`, this method does not expect chunked data and does not have to retrieve
109    /// intermediate merkle nodes.
110    ///
111    /// ## Errors
112    ///
113    /// If the block is not found, `StoreError::BlockNotFound` error is returned.
114    fn get_raw_block(&self, cid: &Cid) -> impl Future<Output = StoreResult<Bytes>> + Send + Sync;
115
116    /// Checks if the store has a block with the given `Cid`.
117    fn has(&self, cid: &Cid) -> impl Future<Output = bool>;
118
119    /// Returns the codecs supported by the store.
120    fn get_supported_codecs(&self) -> HashSet<Codec>;
121
122    /// Returns the allowed maximum block size for IPLD and merkle nodes.
123    /// If there is no limit, `None` is returned.
124    fn get_node_block_max_size(&self) -> Option<u64>;
125
126    /// Returns the allowed maximum block size for raw bytes. If there is no limit, `None` is returned.
127    fn get_raw_block_max_size(&self) -> Option<u64>;
128
129    /// Checks if the store is empty.
130    fn is_empty(&self) -> impl Future<Output = StoreResult<bool>>;
131
132    /// Returns the number of blocks in the store.
133    fn get_size(&self) -> impl Future<Output = StoreResult<u64>>;
134
135    // /// Attempts to remove a node and its dependencies from the store.
136    // ///
137    // /// Returns the number of nodes and blocks removed.
138    // fn remove(&self, cid: &Cid) -> impl Future<Output = StoreResult<usize>>;
139}
140
141/// Helper extension to the `IpldStore` trait.
142pub trait IpldStoreExt: IpldStore {
143    /// Reads all the bytes associated with the given `Cid` into a single `Bytes` type.
144    fn read_all(&self, cid: &Cid) -> impl Future<Output = StoreResult<Bytes>> {
145        async {
146            let mut reader = self.get_bytes(cid).await?;
147            let mut bytes = Vec::new();
148
149            reader
150                .read_to_end(&mut bytes)
151                .await
152                .map_err(StoreError::custom)?;
153
154            Ok(Bytes::from(bytes))
155        }
156    }
157}
158
159/// `IpldStoreSeekable` is a trait that extends the `IpldStore` trait to allow for seeking.
160pub trait IpldStoreSeekable: IpldStore {
161    /// Gets a seekable reader for the underlying bytes associated with the given `Cid`.
162    fn get_seekable_bytes<'a>(
163        &'a self,
164        cid: &'a Cid,
165    ) -> impl Future<Output = StoreResult<Pin<Box<dyn SeekableReader + Send + 'a>>>>;
166}
167
168//--------------------------------------------------------------------------------------------------
169// Trait Implementations
170//--------------------------------------------------------------------------------------------------
171
172impl TryFrom<u64> for Codec {
173    type Error = StoreError;
174
175    fn try_from(value: u64) -> Result<Self, Self::Error> {
176        match value {
177            0x55 => Ok(Codec::Raw),
178            0x71 => Ok(Codec::DagCbor),
179            0x0129 => Ok(Codec::DagJson),
180            0x70 => Ok(Codec::DagPb),
181            _ => Err(StoreError::UnsupportedCodec(value)),
182        }
183    }
184}
185
186impl From<Codec> for u64 {
187    fn from(codec: Codec) -> Self {
188        match codec {
189            Codec::Raw => 0x55,
190            Codec::DagCbor => 0x71,
191            Codec::DagJson => 0x0129,
192            Codec::DagPb => 0x70,
193        }
194    }
195}
196
197impl<T> IpldStoreExt for T where T: IpldStore {}