canic_memory/macros/
storable.rs

1//! Derive `ic-stable-structures` storage traits using Mini CBOR serialization.
2//!
3//! The helper macros below wire types into the Canister Development Kit's
4//! `Storable` trait by delegating to Canic's MiniCBOR helpers. The bounded
5//! variant requires callers to specify a maximum serialized size and whether
6//! the size is fixed; the unbounded variant is suitable for archival data
7//! that can grow, at the cost of larger metadata cells.
8
9/// Implement [`Storable`](ic_stable_structures::storable::Storable) with a
10/// bounded size guarantee.
11#[macro_export]
12macro_rules! impl_storable_bounded {
13    ($ident:ident, $max_size:expr, $is_fixed_size:expr) => {
14        impl $crate::cdk::structures::storable::Storable for $ident {
15            const BOUND: $crate::cdk::structures::storable::Bound =
16                $crate::cdk::structures::storable::Bound::Bounded {
17                    max_size: $max_size,
18                    is_fixed_size: $is_fixed_size,
19                };
20
21            fn to_bytes(&self) -> ::std::borrow::Cow<'_, [u8]> {
22                let bytes = $crate::serialize::serialize(self).unwrap_or_else(|e| {
23                    panic!("impl_storable_bounded: serialize failed: {e}");
24                });
25
26                ::std::borrow::Cow::Owned(bytes)
27            }
28
29            fn into_bytes(self) -> Vec<u8> {
30                $crate::serialize::serialize(&self).unwrap_or_else(|e| {
31                    panic!("impl_storable_bounded: serialize failed: {e}");
32                })
33            }
34
35            fn from_bytes(bytes: ::std::borrow::Cow<'_, [u8]>) -> Self {
36                $crate::serialize::deserialize(&bytes).unwrap_or_else(|e| {
37                    panic!("impl_storable_bounded: deserialize failed: {e}");
38                })
39            }
40        }
41    };
42}
43
44/// Implement [`Storable`](ic_stable_structures::storable::Storable) without a
45/// size bound.
46#[macro_export]
47macro_rules! impl_storable_unbounded {
48    ($ident:ident) => {
49        impl $crate::cdk::structures::storable::Storable for $ident {
50            const BOUND: $crate::cdk::structures::storable::Bound =
51                $crate::cdk::structures::storable::Bound::Unbounded;
52
53            fn to_bytes(&self) -> ::std::borrow::Cow<'_, [u8]> {
54                let bytes = $crate::serialize::serialize(self).unwrap_or_else(|e| {
55                    panic!("impl_storable_unbounded: serialize failed: {e}");
56                });
57
58                ::std::borrow::Cow::Owned(bytes)
59            }
60
61            fn into_bytes(self) -> Vec<u8> {
62                $crate::serialize::serialize(&self).unwrap_or_else(|e| {
63                    panic!("impl_storable_unbounded: serialize failed: {e}");
64                })
65            }
66
67            fn from_bytes(bytes: ::std::borrow::Cow<'_, [u8]>) -> Self {
68                $crate::serialize::deserialize(&bytes).unwrap_or_else(|e| {
69                    panic!("impl_storable_unbounded: deserialize failed: {e}");
70                })
71            }
72        }
73    };
74}
75
76///
77/// TESTS
78///
79
80#[cfg(test)]
81mod tests {
82    use canic_cdk::structures::storable::Storable;
83    use serde::{Deserialize, Serialize};
84    use std::borrow::Cow;
85
86    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
87    struct Sample {
88        v: u32,
89    }
90
91    impl_storable_bounded!(Sample, 32, false);
92
93    #[test]
94    fn bounded_round_trip() {
95        let s = Sample { v: 42 };
96        let bytes = s.to_bytes();
97        let decoded = Sample::from_bytes(bytes);
98
99        assert_eq!(decoded, s);
100    }
101
102    #[test]
103    #[should_panic(expected = "impl_storable_bounded: deserialize failed")]
104    fn bounded_deserialize_panics_with_context() {
105        let bytes = Cow::Owned(vec![0xFF]); // invalid CBOR
106        let _ = Sample::from_bytes(bytes);
107    }
108}