1use ic_principal::Principal;
2use std::borrow::{Borrow, Cow};
3use std::cmp::{Ordering, Reverse};
4use std::convert::{TryFrom, TryInto};
5use std::fmt;
6
7mod tuples;
8
9#[cfg(test)]
10mod tests;
11
12pub trait Storable {
14 fn to_bytes(&self) -> Cow<[u8]>;
18
19 fn from_bytes(bytes: Cow<[u8]>) -> Self;
21
22 const BOUND: Bound;
24
25 fn to_bytes_checked(&self) -> Cow<[u8]> {
28 let bytes = self.to_bytes();
29 if let Bound::Bounded {
30 max_size,
31 is_fixed_size,
32 } = Self::BOUND
33 {
34 if is_fixed_size {
35 assert_eq!(
36 bytes.len(),
37 max_size as usize,
38 "expected a fixed-size element with length {} bytes, but found {} bytes",
39 max_size,
40 bytes.len()
41 );
42 } else {
43 assert!(
44 bytes.len() <= max_size as usize,
45 "expected an element with length <= {} bytes, but found {} bytes",
46 max_size,
47 bytes.len()
48 );
49 }
50 }
51 bytes
52 }
53}
54
55#[derive(Debug, PartialEq)]
56pub enum Bound {
58 Unbounded,
60
61 Bounded {
63 max_size: u32,
65
66 is_fixed_size: bool,
74 },
75}
76
77impl Bound {
78 pub const fn max_size(&self) -> u32 {
80 if let Bound::Bounded { max_size, .. } = self {
81 *max_size
82 } else {
83 panic!("Cannot get max size of unbounded type.");
84 }
85 }
86
87 pub const fn is_fixed_size(&self) -> bool {
89 if let Bound::Bounded { is_fixed_size, .. } = self {
90 *is_fixed_size
91 } else {
92 false
93 }
94 }
95}
96
97#[derive(Eq, Copy, Clone)]
99pub struct Blob<const N: usize> {
100 storage: [u8; N],
101 size: u32,
102}
103
104impl<const N: usize> Blob<N> {
105 pub fn as_slice(&self) -> &[u8] {
107 &self.storage[0..self.len()]
108 }
109
110 pub fn is_empty(&self) -> bool {
112 self.len() == 0
113 }
114
115 pub fn len(&self) -> usize {
117 self.size as usize
118 }
119}
120
121impl<const N: usize> Default for Blob<N> {
122 fn default() -> Self {
123 Self {
124 storage: [0; N],
125 size: 0,
126 }
127 }
128}
129
130#[derive(Debug)]
131pub struct TryFromSliceError;
132
133impl<const N: usize> TryFrom<&[u8]> for Blob<N> {
134 type Error = TryFromSliceError;
135
136 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
137 if value.len() > N {
138 return Err(TryFromSliceError);
139 }
140 let mut result = Self::default();
141 result.storage[0..value.len()].copy_from_slice(value);
142 result.size = value.len() as u32;
143 Ok(result)
144 }
145}
146
147impl<const N: usize> AsRef<[u8]> for Blob<N> {
148 fn as_ref(&self) -> &[u8] {
149 self.as_slice()
150 }
151}
152
153impl<const N: usize> PartialEq for Blob<N> {
154 fn eq(&self, other: &Self) -> bool {
155 self.as_slice().eq(other.as_slice())
156 }
157}
158
159impl<const N: usize> PartialOrd for Blob<N> {
160 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
161 Some(self.cmp(other))
162 }
163}
164
165impl<const N: usize> Ord for Blob<N> {
166 fn cmp(&self, other: &Self) -> Ordering {
167 self.as_slice().cmp(other.as_slice())
168 }
169}
170
171impl<const N: usize> fmt::Debug for Blob<N> {
172 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
173 self.as_slice().fmt(fmt)
174 }
175}
176
177impl<const N: usize> Storable for Blob<N> {
178 fn to_bytes(&self) -> Cow<[u8]> {
179 Cow::Borrowed(self.as_slice())
180 }
181
182 #[inline]
183 fn from_bytes(bytes: Cow<[u8]>) -> Self {
184 Self::try_from(bytes.borrow()).unwrap()
185 }
186
187 const BOUND: Bound = Bound::Bounded {
188 max_size: N as u32,
189 is_fixed_size: false,
190 };
191}
192
193#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
195pub struct UnboundedVecN<const N: usize>(Vec<u8>);
196
197impl<const N: usize> UnboundedVecN<N> {
198 pub const MAX_SIZE: u32 = N as u32;
199
200 pub fn from(slice: &[u8]) -> Self {
201 assert!(
202 slice.len() <= N,
203 "expected a slice with length <= {} bytes, but found {} bytes",
204 N,
205 slice.len()
206 );
207 let mut vec = Vec::with_capacity(N);
208 vec.extend_from_slice(slice);
209 vec.resize(N, 0);
210 Self(vec)
211 }
212}
213
214impl<const N: usize> Default for UnboundedVecN<N> {
215 fn default() -> Self {
216 Self(vec![0; N])
217 }
218}
219
220impl<const N: usize> Storable for UnboundedVecN<N> {
221 fn to_bytes(&self) -> Cow<[u8]> {
222 Cow::Owned(self.0.clone())
223 }
224
225 #[inline]
226 fn from_bytes(bytes: Cow<[u8]>) -> Self {
227 Self(bytes.into_owned())
228 }
229
230 const BOUND: Bound = Bound::Unbounded;
231}
232
233#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
235pub struct BoundedVecN<const N: usize>(Vec<u8>);
236
237impl<const N: usize> BoundedVecN<N> {
238 pub const MAX_SIZE: u32 = N as u32;
239
240 pub fn from(slice: &[u8]) -> Self {
241 assert!(
242 slice.len() <= N,
243 "expected a slice with length <= {} bytes, but found {} bytes",
244 N,
245 slice.len()
246 );
247 let mut vec = Vec::with_capacity(N);
248 vec.extend_from_slice(slice);
249 vec.resize(N, 0);
250 Self(vec)
251 }
252}
253
254impl<const N: usize> Default for BoundedVecN<N> {
255 fn default() -> Self {
256 Self(vec![0; N])
257 }
258}
259
260impl<const N: usize> Storable for BoundedVecN<N> {
261 fn to_bytes(&self) -> Cow<[u8]> {
262 Cow::Owned(self.0.clone())
263 }
264
265 #[inline]
266 fn from_bytes(bytes: Cow<[u8]>) -> Self {
267 Self(bytes.into_owned())
268 }
269
270 const BOUND: Bound = Bound::Bounded {
271 max_size: N as u32,
272 is_fixed_size: false,
273 };
274}
275
276impl Storable for () {
289 fn to_bytes(&self) -> Cow<[u8]> {
290 Cow::Borrowed(&[])
291 }
292
293 #[inline]
294 fn from_bytes(bytes: Cow<[u8]>) -> Self {
295 assert!(bytes.is_empty());
296 }
297
298 const BOUND: Bound = Bound::Bounded {
299 max_size: 0,
300 is_fixed_size: false,
303 };
304}
305
306impl Storable for Vec<u8> {
307 fn to_bytes(&self) -> Cow<[u8]> {
308 Cow::Borrowed(self)
309 }
310
311 #[inline]
312 fn from_bytes(bytes: Cow<[u8]>) -> Self {
313 bytes.into_owned()
314 }
315
316 const BOUND: Bound = Bound::Unbounded;
317}
318
319impl Storable for String {
320 fn to_bytes(&self) -> Cow<[u8]> {
321 Cow::Borrowed(self.as_bytes())
322 }
323
324 #[inline]
325 fn from_bytes(bytes: Cow<[u8]>) -> Self {
326 String::from_utf8(bytes.into_owned()).unwrap()
327 }
328
329 const BOUND: Bound = Bound::Unbounded;
330}
331
332impl Storable for u128 {
333 fn to_bytes(&self) -> Cow<[u8]> {
334 Cow::Owned(self.to_be_bytes().to_vec())
335 }
336
337 #[inline]
338 fn from_bytes(bytes: Cow<[u8]>) -> Self {
339 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
340 }
341
342 const BOUND: Bound = Bound::Bounded {
343 max_size: 16,
344 is_fixed_size: true,
345 };
346}
347
348impl Storable for u64 {
349 fn to_bytes(&self) -> Cow<[u8]> {
350 Cow::Owned(self.to_be_bytes().to_vec())
351 }
352
353 #[inline]
354 fn from_bytes(bytes: Cow<[u8]>) -> Self {
355 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
356 }
357
358 const BOUND: Bound = Bound::Bounded {
359 max_size: 8,
360 is_fixed_size: true,
361 };
362}
363
364impl Storable for f64 {
365 fn to_bytes(&self) -> Cow<[u8]> {
366 Cow::Owned(self.to_be_bytes().to_vec())
367 }
368
369 #[inline]
370 fn from_bytes(bytes: Cow<[u8]>) -> Self {
371 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
372 }
373
374 const BOUND: Bound = Bound::Bounded {
375 max_size: 8,
376 is_fixed_size: true,
377 };
378}
379
380impl Storable for u32 {
381 fn to_bytes(&self) -> Cow<[u8]> {
382 Cow::Owned(self.to_be_bytes().to_vec())
383 }
384
385 #[inline]
386 fn from_bytes(bytes: Cow<[u8]>) -> Self {
387 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
388 }
389
390 const BOUND: Bound = Bound::Bounded {
391 max_size: 4,
392 is_fixed_size: true,
393 };
394}
395
396impl Storable for f32 {
397 fn to_bytes(&self) -> Cow<[u8]> {
398 Cow::Owned(self.to_be_bytes().to_vec())
399 }
400
401 #[inline]
402 fn from_bytes(bytes: Cow<[u8]>) -> Self {
403 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
404 }
405
406 const BOUND: Bound = Bound::Bounded {
407 max_size: 4,
408 is_fixed_size: true,
409 };
410}
411
412impl Storable for u16 {
413 fn to_bytes(&self) -> Cow<[u8]> {
414 Cow::Owned(self.to_be_bytes().to_vec())
415 }
416
417 #[inline]
418 fn from_bytes(bytes: Cow<[u8]>) -> Self {
419 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
420 }
421
422 const BOUND: Bound = Bound::Bounded {
423 max_size: 2,
424 is_fixed_size: true,
425 };
426}
427
428impl Storable for u8 {
429 fn to_bytes(&self) -> Cow<[u8]> {
430 Cow::Owned(self.to_be_bytes().to_vec())
431 }
432
433 #[inline]
434 fn from_bytes(bytes: Cow<[u8]>) -> Self {
435 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
436 }
437
438 const BOUND: Bound = Bound::Bounded {
439 max_size: 1,
440 is_fixed_size: true,
441 };
442}
443
444impl Storable for bool {
445 fn to_bytes(&self) -> Cow<[u8]> {
446 let num: u8 = if *self { 1 } else { 0 };
447 Cow::Owned(num.to_be_bytes().to_vec())
448 }
449
450 #[inline]
451 fn from_bytes(bytes: Cow<[u8]>) -> Self {
452 assert_eq!(bytes.len(), 1);
453 match bytes[0] {
454 0 => false,
455 1 => true,
456 other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
457 }
458 }
459
460 const BOUND: Bound = Bound::Bounded {
461 max_size: 1,
462 is_fixed_size: true,
463 };
464}
465
466impl<const N: usize> Storable for [u8; N] {
467 fn to_bytes(&self) -> Cow<[u8]> {
468 Cow::Borrowed(&self[..])
469 }
470
471 #[inline]
472 fn from_bytes(bytes: Cow<[u8]>) -> Self {
473 assert_eq!(bytes.len(), N);
474 let mut arr = [0; N];
475 arr[0..N].copy_from_slice(&bytes);
476 arr
477 }
478
479 const BOUND: Bound = Bound::Bounded {
480 max_size: N as u32,
481 is_fixed_size: true,
482 };
483}
484
485impl<T: Storable> Storable for Reverse<T> {
486 fn to_bytes(&self) -> Cow<[u8]> {
487 self.0.to_bytes()
488 }
489
490 #[inline]
491 fn from_bytes(bytes: Cow<[u8]>) -> Self {
492 Self(T::from_bytes(bytes))
493 }
494
495 const BOUND: Bound = T::BOUND;
496}
497
498impl<T: Storable> Storable for Option<T> {
499 fn to_bytes(&self) -> Cow<[u8]> {
500 match self {
501 Some(t) => {
502 let mut bytes = t.to_bytes().into_owned();
503 bytes.push(1);
504 Cow::Owned(bytes)
505 }
506 None => Cow::Borrowed(&[0]),
507 }
508 }
509
510 #[inline]
511 fn from_bytes(bytes: Cow<[u8]>) -> Self {
512 match bytes.split_last() {
513 Some((last, rest)) => match last {
514 0 => {
515 assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
516 None
517 }
518 1 => Some(T::from_bytes(Cow::Borrowed(rest))),
519 _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
520 },
521 None => panic!("Invalid Option encoding: expected at least one byte"),
522 }
523 }
524
525 const BOUND: Bound = {
526 match T::BOUND {
527 Bound::Bounded {
528 max_size,
529 is_fixed_size,
530 } => Bound::Bounded {
531 max_size: max_size + 1,
532 is_fixed_size,
533 },
534 Bound::Unbounded => Bound::Unbounded,
535 }
536 };
537}
538
539impl Storable for Principal {
540 fn to_bytes(&self) -> Cow<[u8]> {
541 Cow::Borrowed(self.as_slice())
542 }
543
544 #[inline]
545 fn from_bytes(bytes: Cow<[u8]>) -> Self {
546 Self::from_slice(&bytes)
547 }
548
549 const BOUND: Bound = Bound::Bounded {
550 max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
551 is_fixed_size: false,
552 };
553}
554
555pub(crate) struct Bounds {
556 pub max_size: u32,
557 pub is_fixed_size: bool,
558}
559
560pub(crate) const fn bounds<A: Storable>() -> Bounds {
562 if let Bound::Bounded {
563 max_size,
564 is_fixed_size,
565 } = A::BOUND
566 {
567 Bounds {
568 max_size,
569 is_fixed_size,
570 }
571 } else {
572 panic!("Cannot get bounds of unbounded type.");
573 }
574}
575
576pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
577 if bounds.is_fixed_size {
578 0
579 } else {
580 bytes_to_store_size(bounds.max_size as usize) as u32
581 }
582}
583
584const fn bytes_to_store_size(bytes_size: usize) -> usize {
585 if bytes_size <= u8::MAX as usize {
586 1
587 } else if bytes_size <= u16::MAX as usize {
588 2
589 } else {
590 4
591 }
592}