Skip to main content

axone_objectarium/
state.rs

1use crate::compress::CompressionAlgorithm;
2use crate::crypto::Hash;
3use crate::error::BucketError;
4use crate::error::BucketError::EmptyName;
5use crate::msg;
6use crate::msg::{ObjectResponse, PaginationConfig};
7use cosmwasm_std::{ensure, ensure_ne, Addr, StdError, StdResult, Uint128};
8use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12pub const DATA: Map<Hash, Vec<u8>> = Map::new("DATA");
13
14#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
15pub struct Bucket {
16    /// The owner of the bucket.
17    pub owner: Addr,
18    /// The name of the bucket.
19    pub name: String,
20    /// The configuration for the bucket.
21    pub config: BucketConfig,
22    /// The limits of the bucket.
23    pub limits: BucketLimits,
24    /// The configuration for paginated query.
25    pub pagination: Pagination,
26    /// Some information on the current bucket usage.
27    pub stat: BucketStat,
28}
29
30#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
31pub struct BucketStat {
32    /// The total size of the objects contained in the bucket.
33    pub size: Uint128,
34    /// The total size of the objects contained in the bucket after compression.
35    pub compressed_size: Uint128,
36    /// The number of objects in the bucket.
37    pub object_count: Uint128,
38}
39
40impl Bucket {
41    pub fn try_new(
42        owner: Addr,
43        name: String,
44        config: BucketConfig,
45        limits: BucketLimits,
46        pagination: Pagination,
47    ) -> Result<Self, BucketError> {
48        let n: String = name.split_whitespace().collect();
49        ensure!(!n.is_empty(), EmptyName);
50
51        Ok(Self {
52            owner,
53            name: n,
54            config,
55            limits,
56            pagination,
57            stat: BucketStat {
58                size: Uint128::zero(),
59                compressed_size: Uint128::zero(),
60                object_count: Uint128::zero(),
61            },
62        })
63    }
64}
65
66/// HashAlgorithm is an enumeration that defines the different hash algorithms
67/// supported for hashing the content of objects.
68#[derive(Serialize, Copy, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
69pub enum HashAlgorithm {
70    /// Represents the MD5 algorithm.
71    MD5,
72    /// Represents the SHA-224 algorithm.
73    Sha224,
74    /// Represents the SHA-256 algorithm.
75    #[default]
76    Sha256,
77    /// Represents the SHA-384 algorithm.
78    Sha384,
79    /// Represents the SHA-512 algorithm.
80    Sha512,
81}
82
83impl From<msg::HashAlgorithm> for HashAlgorithm {
84    fn from(algorithm: msg::HashAlgorithm) -> Self {
85        match algorithm {
86            msg::HashAlgorithm::MD5 => HashAlgorithm::MD5,
87            msg::HashAlgorithm::Sha224 => HashAlgorithm::Sha224,
88            msg::HashAlgorithm::Sha256 => HashAlgorithm::Sha256,
89            msg::HashAlgorithm::Sha384 => HashAlgorithm::Sha384,
90            msg::HashAlgorithm::Sha512 => HashAlgorithm::Sha512,
91        }
92    }
93}
94
95impl From<HashAlgorithm> for msg::HashAlgorithm {
96    fn from(algorithm: HashAlgorithm) -> Self {
97        match algorithm {
98            HashAlgorithm::MD5 => msg::HashAlgorithm::MD5,
99            HashAlgorithm::Sha224 => msg::HashAlgorithm::Sha224,
100            HashAlgorithm::Sha256 => msg::HashAlgorithm::Sha256,
101            HashAlgorithm::Sha384 => msg::HashAlgorithm::Sha384,
102            HashAlgorithm::Sha512 => msg::HashAlgorithm::Sha512,
103        }
104    }
105}
106
107impl From<msg::CompressionAlgorithm> for CompressionAlgorithm {
108    fn from(algorithm: msg::CompressionAlgorithm) -> Self {
109        match algorithm {
110            msg::CompressionAlgorithm::Passthrough => CompressionAlgorithm::Passthrough,
111            msg::CompressionAlgorithm::Snappy => CompressionAlgorithm::Snappy,
112            msg::CompressionAlgorithm::Lzma => CompressionAlgorithm::Lzma,
113        }
114    }
115}
116
117impl From<CompressionAlgorithm> for msg::CompressionAlgorithm {
118    fn from(algorithm: CompressionAlgorithm) -> Self {
119        match algorithm {
120            CompressionAlgorithm::Passthrough => msg::CompressionAlgorithm::Passthrough,
121            CompressionAlgorithm::Snappy => msg::CompressionAlgorithm::Snappy,
122            CompressionAlgorithm::Lzma => msg::CompressionAlgorithm::Lzma,
123        }
124    }
125}
126
127/// BucketConfig is the type of the configuration of a bucket.
128///
129/// The configuration is set at the instantiation of the bucket, and is immutable and cannot be changed.
130#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
131pub struct BucketConfig {
132    /// The algorithm used to hash the content of the objects to generate the id of the objects.
133    /// The algorithm is optional and if not set, the default algorithm is used.
134    ///
135    /// The default algorithm is Sha256.
136    pub hash_algorithm: HashAlgorithm,
137    /// The accepted compression algorithms for the objects in the bucket.
138    ///
139    /// The default is all compression algorithms.
140    pub accepted_compression_algorithms: Vec<CompressionAlgorithm>,
141}
142
143impl BucketConfig {
144    fn try_new(
145        hash_algorithm: HashAlgorithm,
146        accepted_compression_algorithms: Vec<CompressionAlgorithm>,
147    ) -> StdResult<BucketConfig> {
148        ensure!(
149            !accepted_compression_algorithms.is_empty(),
150            StdError::generic_err("'accepted_compression_algorithms' cannot be empty")
151        );
152
153        Ok(BucketConfig {
154            hash_algorithm,
155            accepted_compression_algorithms,
156        })
157    }
158}
159
160impl TryFrom<msg::BucketConfig> for BucketConfig {
161    type Error = StdError;
162
163    fn try_from(config: msg::BucketConfig) -> StdResult<BucketConfig> {
164        BucketConfig::try_new(
165            config.hash_algorithm.into(),
166            config
167                .accepted_compression_algorithms
168                .into_iter()
169                .map(Into::into)
170                .collect(),
171        )
172    }
173}
174
175impl From<BucketConfig> for msg::BucketConfig {
176    fn from(config: BucketConfig) -> Self {
177        msg::BucketConfig {
178            hash_algorithm: config.hash_algorithm.into(),
179            accepted_compression_algorithms: config
180                .accepted_compression_algorithms
181                .into_iter()
182                .map(Into::into)
183                .collect(),
184        }
185    }
186}
187
188/// BucketLimits is the type of the limits of a bucket.
189///
190/// The limits are optional and if not set, there is no limit.
191#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
192pub struct BucketLimits {
193    /// The maximum total size of the objects in the bucket.
194    pub max_total_size: Option<Uint128>,
195    /// The maximum number of objects in the bucket.
196    pub max_objects: Option<Uint128>,
197    /// The maximum size of the objects in the bucket.
198    pub max_object_size: Option<Uint128>,
199    /// The maximum number of pins in the bucket for an object.
200    pub max_object_pins: Option<Uint128>,
201}
202
203impl From<BucketLimits> for msg::BucketLimits {
204    fn from(limits: BucketLimits) -> Self {
205        msg::BucketLimits {
206            max_total_size: limits.max_total_size,
207            max_objects: limits.max_objects,
208            max_object_size: limits.max_object_size,
209            max_object_pins: limits.max_object_pins,
210        }
211    }
212}
213
214impl From<BucketStat> for msg::BucketStat {
215    fn from(stat: BucketStat) -> Self {
216        msg::BucketStat {
217            size: stat.size,
218            compressed_size: stat.compressed_size,
219            object_count: stat.object_count,
220        }
221    }
222}
223impl BucketLimits {
224    fn try_new(
225        max_total_size: Option<Uint128>,
226        max_objects: Option<Uint128>,
227        max_object_size: Option<Uint128>,
228        max_object_pins: Option<Uint128>,
229    ) -> StdResult<BucketLimits> {
230        ensure_ne!(
231            max_total_size,
232            Some(Uint128::zero()),
233            StdError::generic_err("'max_total_size' cannot be zero")
234        );
235        ensure_ne!(
236            max_objects,
237            Some(Uint128::zero()),
238            StdError::generic_err("'max_objects' cannot be zero")
239        );
240        ensure_ne!(
241            max_object_size,
242            Some(Uint128::zero()),
243            StdError::generic_err("'max_object_size' cannot be zero")
244        );
245        ensure!(
246            !matches!(
247                (max_total_size, max_object_size),
248                (Some(max_total_size), Some(max_object_size)) if max_total_size < max_object_size
249            ),
250            StdError::generic_err("'max_total_size' cannot be less than 'max_object_size'")
251        );
252
253        Ok(BucketLimits {
254            max_total_size,
255            max_objects,
256            max_object_size,
257            max_object_pins,
258        })
259    }
260}
261
262impl TryFrom<msg::BucketLimits> for BucketLimits {
263    type Error = StdError;
264
265    fn try_from(limits: msg::BucketLimits) -> StdResult<BucketLimits> {
266        BucketLimits::try_new(
267            limits.max_total_size,
268            limits.max_objects,
269            limits.max_object_size,
270            limits.max_object_pins,
271        )
272    }
273}
274/// Pagination is the type carrying configuration for paginated queries.
275#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
276pub struct Pagination {
277    /// The maximum elements a page can contain.
278    pub max_page_size: u32,
279    /// The default number of elements in a page.
280    pub default_page_size: u32,
281}
282
283const MAX_PAGE_MAX_SIZE: u32 = u32::MAX - 1;
284
285impl Pagination {
286    fn try_new(max_page_size: u32, default_page_size: u32) -> StdResult<Pagination> {
287        ensure!(
288            max_page_size <= MAX_PAGE_MAX_SIZE,
289            StdError::generic_err("'max_page_size' cannot exceed 'u32::MAX - 1'")
290        );
291        ensure_ne!(
292            default_page_size,
293            0,
294            StdError::generic_err("'default_page_size' cannot be zero")
295        );
296        ensure!(
297            default_page_size <= max_page_size,
298            StdError::generic_err("'default_page_size' cannot exceed 'max_page_size'")
299        );
300
301        Ok(Pagination {
302            max_page_size,
303            default_page_size,
304        })
305    }
306}
307
308impl From<Pagination> for PaginationConfig {
309    fn from(value: Pagination) -> Self {
310        PaginationConfig {
311            max_page_size: value.max_page_size,
312            default_page_size: value.default_page_size,
313        }
314    }
315}
316
317impl TryFrom<PaginationConfig> for Pagination {
318    type Error = StdError;
319
320    fn try_from(value: PaginationConfig) -> StdResult<Pagination> {
321        Pagination::try_new(value.max_page_size, value.default_page_size)
322    }
323}
324
325pub const BUCKET: Item<Bucket> = Item::new("bucket");
326
327#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
328pub struct Object {
329    /// The id of the object.
330    pub id: Hash,
331    /// The owner of the object.
332    pub owner: Addr,
333    /// The size of the object.
334    pub size: Uint128,
335    /// The number of pin on this object.
336    pub pin_count: Uint128,
337    /// The compression algorithm used to compress the object.
338    pub compression: CompressionAlgorithm,
339    /// The size of the object after compression.
340    pub compressed_size: Uint128,
341}
342
343impl From<&Object> for ObjectResponse {
344    fn from(object: &Object) -> Self {
345        ObjectResponse {
346            id: object.id.to_string(),
347            size: object.size,
348            owner: object.owner.clone().into(),
349            is_pinned: object.pin_count > Uint128::zero(),
350            compressed_size: object.compressed_size,
351            compression_algorithm: object.compression.into(),
352        }
353    }
354}
355
356pub struct ObjectIndexes<'a> {
357    pub owner: MultiIndex<'a, Addr, Object, Hash>,
358}
359
360impl IndexList<Object> for ObjectIndexes<'_> {
361    fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Object>> + '_> {
362        let owner: &dyn Index<Object> = &self.owner;
363        Box::new(vec![owner].into_iter())
364    }
365}
366
367pub fn objects<'a>() -> IndexedMap<Hash, Object, ObjectIndexes<'a>> {
368    IndexedMap::new(
369        "OBJECT",
370        ObjectIndexes {
371            owner: MultiIndex::new(|_, object| object.owner.clone(), "OBJECT", "OBJECT__OWNER"),
372        },
373    )
374}
375
376#[derive(Serialize, Deserialize, Clone)]
377pub struct Pin {
378    /// The id of the object.
379    pub id: Hash,
380    /// The address that pinned the object.
381    pub address: Addr,
382}
383
384pub struct PinIndexes<'a> {
385    pub object: MultiIndex<'a, Hash, Pin, (Hash, Addr)>,
386}
387
388impl IndexList<Pin> for PinIndexes<'_> {
389    fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Pin>> + '_> {
390        let object: &dyn Index<Pin> = &self.object;
391        Box::new(vec![object].into_iter())
392    }
393}
394
395pub fn pins<'a>() -> IndexedMap<(Hash, Addr), Pin, PinIndexes<'a>> {
396    IndexedMap::new(
397        "PIN",
398        PinIndexes {
399            object: MultiIndex::new(|_, pin| pin.id.clone(), "PIN", "PIN__OBJECT"),
400        },
401    )
402}