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 {}