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 into_bytes(self) -> Vec<u8>;
23
24 fn from_bytes(bytes: Cow<[u8]>) -> Self;
26
27 const BOUND: Bound;
29
30 fn to_bytes_checked(&self) -> Cow<'_, [u8]> {
32 let bytes = self.to_bytes();
33 Self::check_bounds(&bytes);
34 bytes
35 }
36
37 fn into_bytes_checked(self) -> Vec<u8>
39 where
40 Self: Sized,
41 {
42 let bytes = self.into_bytes();
43 Self::check_bounds(&bytes);
44 bytes
45 }
46
47 #[inline]
49 fn check_bounds(bytes: &[u8]) {
50 if let Bound::Bounded {
51 max_size,
52 is_fixed_size,
53 } = Self::BOUND
54 {
55 let actual = bytes.len();
56 if is_fixed_size {
57 assert_eq!(
58 actual, max_size as usize,
59 "expected a fixed-size element with length {} bytes, but found {} bytes",
60 max_size, actual
61 );
62 } else {
63 assert!(
64 actual <= max_size as usize,
65 "expected an element with length <= {} bytes, but found {} bytes",
66 max_size,
67 actual
68 );
69 }
70 }
71 }
72}
73
74#[derive(Debug, PartialEq)]
75pub enum Bound {
77 Unbounded,
79
80 Bounded {
82 max_size: u32,
84
85 is_fixed_size: bool,
93 },
94}
95
96impl Bound {
97 pub const fn max_size(&self) -> u32 {
99 if let Bound::Bounded { max_size, .. } = self {
100 *max_size
101 } else {
102 panic!("Cannot get max size of unbounded type.");
103 }
104 }
105
106 pub const fn is_fixed_size(&self) -> bool {
108 if let Bound::Bounded { is_fixed_size, .. } = self {
109 *is_fixed_size
110 } else {
111 false
112 }
113 }
114}
115
116#[derive(Eq, Copy, Clone)]
118pub struct Blob<const N: usize> {
119 storage: [u8; N],
120 size: u32,
121}
122
123impl<const N: usize> Blob<N> {
124 pub fn as_slice(&self) -> &[u8] {
126 &self.storage[0..self.len()]
127 }
128
129 pub fn is_empty(&self) -> bool {
131 self.len() == 0
132 }
133
134 pub fn len(&self) -> usize {
136 self.size as usize
137 }
138}
139
140impl<const N: usize> Default for Blob<N> {
141 fn default() -> Self {
142 Self {
143 storage: [0; N],
144 size: 0,
145 }
146 }
147}
148
149#[derive(Debug)]
150pub struct TryFromSliceError;
151
152impl<const N: usize> TryFrom<&[u8]> for Blob<N> {
153 type Error = TryFromSliceError;
154
155 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
156 if value.len() > N {
157 return Err(TryFromSliceError);
158 }
159 let mut result = Self::default();
160 result.storage[0..value.len()].copy_from_slice(value);
161 result.size = value.len() as u32;
162 Ok(result)
163 }
164}
165
166impl<const N: usize> AsRef<[u8]> for Blob<N> {
167 fn as_ref(&self) -> &[u8] {
168 self.as_slice()
169 }
170}
171
172impl<const N: usize> PartialEq for Blob<N> {
173 fn eq(&self, other: &Self) -> bool {
174 self.as_slice().eq(other.as_slice())
175 }
176}
177
178impl<const N: usize> PartialOrd for Blob<N> {
179 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
180 Some(self.cmp(other))
181 }
182}
183
184impl<const N: usize> Ord for Blob<N> {
185 fn cmp(&self, other: &Self) -> Ordering {
186 self.as_slice().cmp(other.as_slice())
187 }
188}
189
190impl<const N: usize> fmt::Debug for Blob<N> {
191 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
192 self.as_slice().fmt(fmt)
193 }
194}
195
196impl<const N: usize> Storable for Blob<N> {
197 #[inline]
198 fn to_bytes(&self) -> Cow<'_, [u8]> {
199 Cow::Borrowed(self.as_slice())
200 }
201
202 #[inline]
203 fn into_bytes(self) -> Vec<u8> {
204 self.as_slice().to_vec()
205 }
206
207 #[inline]
208 fn from_bytes(bytes: Cow<[u8]>) -> Self {
209 Self::try_from(bytes.borrow()).unwrap()
210 }
211
212 const BOUND: Bound = Bound::Bounded {
213 max_size: N as u32,
214 is_fixed_size: false,
215 };
216}
217
218#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
220pub struct UnboundedVecN<const N: usize>(Vec<u8>);
221
222impl<const N: usize> UnboundedVecN<N> {
223 pub const MAX_SIZE: u32 = N as u32;
224
225 pub fn from(slice: &[u8]) -> Self {
226 assert!(
227 slice.len() <= N,
228 "expected a slice with length <= {} bytes, but found {} bytes",
229 N,
230 slice.len()
231 );
232 let mut vec = Vec::with_capacity(N);
233 vec.extend_from_slice(slice);
234 vec.resize(N, 0);
235 Self(vec)
236 }
237}
238
239impl<const N: usize> Default for UnboundedVecN<N> {
240 fn default() -> Self {
241 Self(vec![0; N])
242 }
243}
244
245impl<const N: usize> Storable for UnboundedVecN<N> {
246 fn to_bytes(&self) -> Cow<'_, [u8]> {
247 Cow::Owned(self.0.clone())
248 }
249
250 #[inline]
251 fn into_bytes(self) -> Vec<u8> {
252 self.0
253 }
254
255 #[inline]
256 fn from_bytes(bytes: Cow<[u8]>) -> Self {
257 Self(bytes.into_owned())
258 }
259
260 const BOUND: Bound = Bound::Unbounded;
261}
262
263#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
265pub struct BoundedVecN<const N: usize>(Vec<u8>);
266
267impl<const N: usize> BoundedVecN<N> {
268 pub const MAX_SIZE: u32 = N as u32;
269
270 pub fn from(slice: &[u8]) -> Self {
271 assert!(
272 slice.len() <= N,
273 "expected a slice with length <= {} bytes, but found {} bytes",
274 N,
275 slice.len()
276 );
277 let mut vec = Vec::with_capacity(N);
278 vec.extend_from_slice(slice);
279 vec.resize(N, 0);
280 Self(vec)
281 }
282}
283
284impl<const N: usize> Default for BoundedVecN<N> {
285 fn default() -> Self {
286 Self(vec![0; N])
287 }
288}
289
290impl<const N: usize> Storable for BoundedVecN<N> {
291 fn to_bytes(&self) -> Cow<'_, [u8]> {
292 Cow::Owned(self.0.clone())
293 }
294
295 #[inline]
296 fn into_bytes(self) -> Vec<u8> {
297 self.0
298 }
299
300 #[inline]
301 fn from_bytes(bytes: Cow<[u8]>) -> Self {
302 Self(bytes.into_owned())
303 }
304
305 const BOUND: Bound = Bound::Bounded {
306 max_size: N as u32,
307 is_fixed_size: false,
308 };
309}
310
311impl Storable for () {
324 #[inline]
325 fn to_bytes(&self) -> Cow<'_, [u8]> {
326 Cow::Borrowed(&[])
327 }
328
329 #[inline]
330 fn into_bytes(self) -> Vec<u8> {
331 Vec::new()
332 }
333
334 #[inline]
335 fn from_bytes(bytes: Cow<[u8]>) -> Self {
336 assert!(bytes.is_empty());
337 }
338
339 const BOUND: Bound = Bound::Bounded {
340 max_size: 0,
341 is_fixed_size: false,
344 };
345}
346
347impl Storable for Vec<u8> {
348 #[inline]
349 fn to_bytes(&self) -> Cow<'_, [u8]> {
350 Cow::Borrowed(self)
351 }
352
353 #[inline]
354 fn into_bytes(self) -> Vec<u8> {
355 self
356 }
357
358 #[inline]
359 fn from_bytes(bytes: Cow<[u8]>) -> Self {
360 bytes.into_owned()
361 }
362
363 const BOUND: Bound = Bound::Unbounded;
364}
365
366impl Storable for String {
367 #[inline]
368 fn to_bytes(&self) -> Cow<'_, [u8]> {
369 Cow::Borrowed(self.as_bytes())
370 }
371
372 #[inline]
373 fn into_bytes(self) -> Vec<u8> {
374 self.into_bytes()
375 }
376
377 #[inline]
378 fn from_bytes(bytes: Cow<[u8]>) -> Self {
379 String::from_utf8(bytes.into_owned()).unwrap()
380 }
381
382 const BOUND: Bound = Bound::Unbounded;
383}
384
385impl Storable for u128 {
386 #[inline]
387 fn to_bytes(&self) -> Cow<'_, [u8]> {
388 Cow::Owned(self.into_bytes())
389 }
390
391 #[inline]
392 fn into_bytes(self) -> Vec<u8> {
393 self.to_be_bytes().to_vec()
394 }
395
396 #[inline]
397 fn from_bytes(bytes: Cow<[u8]>) -> Self {
398 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
399 }
400
401 const BOUND: Bound = Bound::Bounded {
402 max_size: 16,
403 is_fixed_size: true,
404 };
405}
406
407impl Storable for u64 {
408 #[inline]
409 fn to_bytes(&self) -> Cow<'_, [u8]> {
410 Cow::Owned(self.into_bytes())
411 }
412
413 #[inline]
414 fn into_bytes(self) -> Vec<u8> {
415 self.to_be_bytes().to_vec()
416 }
417
418 #[inline]
419 fn from_bytes(bytes: Cow<[u8]>) -> Self {
420 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
421 }
422
423 const BOUND: Bound = Bound::Bounded {
424 max_size: 8,
425 is_fixed_size: true,
426 };
427}
428
429impl Storable for f64 {
430 #[inline]
431 fn to_bytes(&self) -> Cow<'_, [u8]> {
432 Cow::Owned(self.into_bytes())
433 }
434
435 #[inline]
436 fn into_bytes(self) -> Vec<u8> {
437 self.to_be_bytes().to_vec()
438 }
439
440 #[inline]
441 fn from_bytes(bytes: Cow<[u8]>) -> Self {
442 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
443 }
444
445 const BOUND: Bound = Bound::Bounded {
446 max_size: 8,
447 is_fixed_size: true,
448 };
449}
450
451impl Storable for u32 {
452 #[inline]
453 fn to_bytes(&self) -> Cow<'_, [u8]> {
454 Cow::Owned(self.into_bytes())
455 }
456
457 #[inline]
458 fn into_bytes(self) -> Vec<u8> {
459 self.to_be_bytes().to_vec()
460 }
461
462 #[inline]
463 fn from_bytes(bytes: Cow<[u8]>) -> Self {
464 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
465 }
466
467 const BOUND: Bound = Bound::Bounded {
468 max_size: 4,
469 is_fixed_size: true,
470 };
471}
472
473impl Storable for f32 {
474 #[inline]
475 fn to_bytes(&self) -> Cow<'_, [u8]> {
476 Cow::Owned(self.into_bytes())
477 }
478
479 #[inline]
480 fn into_bytes(self) -> Vec<u8> {
481 self.to_be_bytes().to_vec()
482 }
483
484 #[inline]
485 fn from_bytes(bytes: Cow<[u8]>) -> Self {
486 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
487 }
488
489 const BOUND: Bound = Bound::Bounded {
490 max_size: 4,
491 is_fixed_size: true,
492 };
493}
494
495impl Storable for u16 {
496 #[inline]
497 fn to_bytes(&self) -> Cow<'_, [u8]> {
498 Cow::Owned(self.into_bytes())
499 }
500
501 #[inline]
502 fn into_bytes(self) -> Vec<u8> {
503 self.to_be_bytes().to_vec()
504 }
505
506 #[inline]
507 fn from_bytes(bytes: Cow<[u8]>) -> Self {
508 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
509 }
510
511 const BOUND: Bound = Bound::Bounded {
512 max_size: 2,
513 is_fixed_size: true,
514 };
515}
516
517impl Storable for u8 {
518 #[inline]
519 fn to_bytes(&self) -> Cow<'_, [u8]> {
520 Cow::Owned(self.into_bytes())
521 }
522
523 #[inline]
524 fn into_bytes(self) -> Vec<u8> {
525 self.to_be_bytes().to_vec()
526 }
527
528 #[inline]
529 fn from_bytes(bytes: Cow<[u8]>) -> Self {
530 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
531 }
532
533 const BOUND: Bound = Bound::Bounded {
534 max_size: 1,
535 is_fixed_size: true,
536 };
537}
538
539impl Storable for bool {
540 fn to_bytes(&self) -> Cow<'_, [u8]> {
541 Cow::Owned(if *self { 1_u8 } else { 0_u8 }.to_be_bytes().to_vec())
542 }
543
544 fn into_bytes(self) -> Vec<u8> {
545 if self { 1_u8 } else { 0_u8 }.to_be_bytes().to_vec()
546 }
547
548 #[inline]
549 fn from_bytes(bytes: Cow<[u8]>) -> Self {
550 assert_eq!(bytes.len(), 1);
551 match bytes[0] {
552 0 => false,
553 1 => true,
554 other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
555 }
556 }
557
558 const BOUND: Bound = Bound::Bounded {
559 max_size: 1,
560 is_fixed_size: true,
561 };
562}
563
564impl<const N: usize> Storable for [u8; N] {
565 #[inline]
566 fn to_bytes(&self) -> Cow<'_, [u8]> {
567 Cow::Borrowed(&self[..])
568 }
569
570 #[inline]
571 fn into_bytes(self) -> Vec<u8> {
572 self.to_vec()
573 }
574
575 #[inline]
576 fn from_bytes(bytes: Cow<[u8]>) -> Self {
577 assert_eq!(bytes.len(), N);
578 let mut arr = [0; N];
579 arr[0..N].copy_from_slice(&bytes);
580 arr
581 }
582
583 const BOUND: Bound = Bound::Bounded {
584 max_size: N as u32,
585 is_fixed_size: true,
586 };
587}
588
589impl<T: Storable> Storable for Reverse<T> {
590 #[inline]
591 fn to_bytes(&self) -> Cow<'_, [u8]> {
592 self.0.to_bytes()
593 }
594
595 #[inline]
596 fn into_bytes(self) -> Vec<u8> {
597 self.0.into_bytes()
598 }
599
600 #[inline]
601 fn from_bytes(bytes: Cow<[u8]>) -> Self {
602 Self(T::from_bytes(bytes))
603 }
604
605 const BOUND: Bound = T::BOUND;
606}
607
608impl<T: Storable> Storable for Option<T> {
609 fn to_bytes(&self) -> Cow<'_, [u8]> {
610 match self {
611 Some(t) => {
612 let mut bytes = t.to_bytes().into_owned();
613 bytes.push(1);
614 Cow::Owned(bytes)
615 }
616 None => Cow::Borrowed(&[0]),
617 }
618 }
619
620 fn into_bytes(self) -> Vec<u8> {
621 match self {
622 Some(t) => {
623 let mut bytes = t.into_bytes();
624 bytes.push(1);
625 bytes
626 }
627 None => vec![0],
628 }
629 }
630
631 #[inline]
632 fn from_bytes(bytes: Cow<[u8]>) -> Self {
633 match bytes.split_last() {
634 Some((last, rest)) => match last {
635 0 => {
636 assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
637 None
638 }
639 1 => Some(T::from_bytes(Cow::Borrowed(rest))),
640 _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
641 },
642 None => panic!("Invalid Option encoding: expected at least one byte"),
643 }
644 }
645
646 const BOUND: Bound = {
647 match T::BOUND {
648 Bound::Bounded {
649 max_size,
650 is_fixed_size,
651 } => Bound::Bounded {
652 max_size: max_size + 1,
653 is_fixed_size,
654 },
655 Bound::Unbounded => Bound::Unbounded,
656 }
657 };
658}
659
660impl Storable for Principal {
661 fn to_bytes(&self) -> Cow<'_, [u8]> {
662 Cow::Borrowed(self.as_slice())
663 }
664
665 fn into_bytes(self) -> Vec<u8> {
666 self.as_slice().to_vec()
667 }
668
669 #[inline]
670 fn from_bytes(bytes: Cow<[u8]>) -> Self {
671 Self::from_slice(&bytes)
672 }
673
674 const BOUND: Bound = Bound::Bounded {
675 max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
676 is_fixed_size: false,
677 };
678}
679
680pub(crate) struct Bounds {
681 pub max_size: u32,
682 pub is_fixed_size: bool,
683}
684
685pub(crate) const fn bounds<A: Storable>() -> Bounds {
687 if let Bound::Bounded {
688 max_size,
689 is_fixed_size,
690 } = A::BOUND
691 {
692 Bounds {
693 max_size,
694 is_fixed_size,
695 }
696 } else {
697 panic!("Cannot get bounds of unbounded type.");
698 }
699}
700
701pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
702 if bounds.is_fixed_size {
703 0
704 } else {
705 bytes_to_store_size(bounds.max_size as usize) as u32
706 }
707}
708
709const fn bytes_to_store_size(bytes_size: usize) -> usize {
710 if bytes_size <= u8::MAX as usize {
711 1
712 } else if bytes_size <= u16::MAX as usize {
713 2
714 } else {
715 4
716 }
717}