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 pub owner: Addr,
18 pub name: String,
20 pub config: BucketConfig,
22 pub limits: BucketLimits,
24 pub pagination: Pagination,
26 pub stat: BucketStat,
28}
29
30#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
31pub struct BucketStat {
32 pub size: Uint128,
34 pub compressed_size: Uint128,
36 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#[derive(Serialize, Copy, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
69pub enum HashAlgorithm {
70 MD5,
72 Sha224,
74 #[default]
76 Sha256,
77 Sha384,
79 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
131pub struct BucketConfig {
132 pub hash_algorithm: HashAlgorithm,
137 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
192pub struct BucketLimits {
193 pub max_total_size: Option<Uint128>,
195 pub max_objects: Option<Uint128>,
197 pub max_object_size: Option<Uint128>,
199 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
276pub struct Pagination {
277 pub max_page_size: u32,
279 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 pub id: Hash,
331 pub owner: Addr,
333 pub size: Uint128,
335 pub pin_count: Uint128,
337 pub compression: CompressionAlgorithm,
339 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 pub id: Hash,
380 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}