ic_stable_structures/
storable.rs1use 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 fn from_bytes(bytes: Cow<[u8]>) -> Self {
183 Self::try_from(bytes.borrow()).unwrap()
184 }
185
186 const BOUND: Bound = Bound::Bounded {
187 max_size: N as u32,
188 is_fixed_size: false,
189 };
190}
191
192impl Storable for () {
205 fn to_bytes(&self) -> Cow<[u8]> {
206 Cow::Borrowed(&[])
207 }
208
209 fn from_bytes(bytes: Cow<[u8]>) -> Self {
210 assert!(bytes.is_empty());
211 }
212
213 const BOUND: Bound = Bound::Bounded {
214 max_size: 0,
215 is_fixed_size: false,
218 };
219}
220
221impl Storable for Vec<u8> {
222 fn to_bytes(&self) -> Cow<[u8]> {
223 Cow::Borrowed(self)
224 }
225
226 fn from_bytes(bytes: Cow<[u8]>) -> Self {
227 bytes.to_vec()
228 }
229
230 const BOUND: Bound = Bound::Unbounded;
231}
232
233impl Storable for String {
234 fn to_bytes(&self) -> Cow<[u8]> {
235 Cow::Borrowed(self.as_bytes())
236 }
237
238 fn from_bytes(bytes: Cow<[u8]>) -> Self {
239 String::from_utf8(bytes.to_vec()).unwrap()
240 }
241
242 const BOUND: Bound = Bound::Unbounded;
243}
244
245impl Storable for u128 {
246 fn to_bytes(&self) -> Cow<[u8]> {
247 Cow::Owned(self.to_be_bytes().to_vec())
248 }
249
250 fn from_bytes(bytes: Cow<[u8]>) -> Self {
251 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
252 }
253
254 const BOUND: Bound = Bound::Bounded {
255 max_size: 16,
256 is_fixed_size: true,
257 };
258}
259
260impl Storable for u64 {
261 fn to_bytes(&self) -> Cow<[u8]> {
262 Cow::Owned(self.to_be_bytes().to_vec())
263 }
264
265 fn from_bytes(bytes: Cow<[u8]>) -> Self {
266 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
267 }
268
269 const BOUND: Bound = Bound::Bounded {
270 max_size: 8,
271 is_fixed_size: true,
272 };
273}
274
275impl Storable for f64 {
276 fn to_bytes(&self) -> Cow<[u8]> {
277 Cow::Owned(self.to_be_bytes().to_vec())
278 }
279
280 fn from_bytes(bytes: Cow<[u8]>) -> Self {
281 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
282 }
283
284 const BOUND: Bound = Bound::Bounded {
285 max_size: 8,
286 is_fixed_size: true,
287 };
288}
289
290impl Storable for u32 {
291 fn to_bytes(&self) -> Cow<[u8]> {
292 Cow::Owned(self.to_be_bytes().to_vec())
293 }
294
295 fn from_bytes(bytes: Cow<[u8]>) -> Self {
296 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
297 }
298
299 const BOUND: Bound = Bound::Bounded {
300 max_size: 4,
301 is_fixed_size: true,
302 };
303}
304
305impl Storable for f32 {
306 fn to_bytes(&self) -> Cow<[u8]> {
307 Cow::Owned(self.to_be_bytes().to_vec())
308 }
309
310 fn from_bytes(bytes: Cow<[u8]>) -> Self {
311 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
312 }
313
314 const BOUND: Bound = Bound::Bounded {
315 max_size: 4,
316 is_fixed_size: true,
317 };
318}
319
320impl Storable for u16 {
321 fn to_bytes(&self) -> Cow<[u8]> {
322 Cow::Owned(self.to_be_bytes().to_vec())
323 }
324
325 fn from_bytes(bytes: Cow<[u8]>) -> Self {
326 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
327 }
328
329 const BOUND: Bound = Bound::Bounded {
330 max_size: 2,
331 is_fixed_size: true,
332 };
333}
334
335impl Storable for u8 {
336 fn to_bytes(&self) -> Cow<[u8]> {
337 Cow::Owned(self.to_be_bytes().to_vec())
338 }
339
340 fn from_bytes(bytes: Cow<[u8]>) -> Self {
341 Self::from_be_bytes(bytes.as_ref().try_into().unwrap())
342 }
343
344 const BOUND: Bound = Bound::Bounded {
345 max_size: 1,
346 is_fixed_size: true,
347 };
348}
349
350impl Storable for bool {
351 fn to_bytes(&self) -> Cow<[u8]> {
352 let num: u8 = if *self { 1 } else { 0 };
353 Cow::Owned(num.to_be_bytes().to_vec())
354 }
355
356 fn from_bytes(bytes: Cow<[u8]>) -> Self {
357 assert_eq!(bytes.len(), 1);
358 match bytes[0] {
359 0 => false,
360 1 => true,
361 other => panic!("Invalid bool encoding: expected 0 or 1, found {}", other),
362 }
363 }
364
365 const BOUND: Bound = Bound::Bounded {
366 max_size: 1,
367 is_fixed_size: true,
368 };
369}
370
371impl<const N: usize> Storable for [u8; N] {
372 fn to_bytes(&self) -> Cow<[u8]> {
373 Cow::Borrowed(&self[..])
374 }
375
376 fn from_bytes(bytes: Cow<[u8]>) -> Self {
377 assert_eq!(bytes.len(), N);
378 let mut arr = [0; N];
379 arr[0..N].copy_from_slice(&bytes);
380 arr
381 }
382
383 const BOUND: Bound = Bound::Bounded {
384 max_size: N as u32,
385 is_fixed_size: true,
386 };
387}
388
389impl<T: Storable> Storable for Reverse<T> {
390 fn to_bytes(&self) -> Cow<[u8]> {
391 self.0.to_bytes()
392 }
393
394 fn from_bytes(bytes: Cow<[u8]>) -> Self {
395 Self(T::from_bytes(bytes))
396 }
397
398 const BOUND: Bound = T::BOUND;
399}
400
401impl<T: Storable> Storable for Option<T> {
402 fn to_bytes(&self) -> Cow<[u8]> {
403 match self {
404 Some(t) => {
405 let mut bytes = t.to_bytes().into_owned();
406 bytes.push(1);
407 Cow::Owned(bytes)
408 }
409 None => Cow::Borrowed(&[0]),
410 }
411 }
412
413 fn from_bytes(bytes: Cow<[u8]>) -> Self {
414 match bytes.split_last() {
415 Some((last, rest)) => match last {
416 0 => {
417 assert!(rest.is_empty(), "Invalid Option encoding: unexpected prefix before the None marker: {rest:?}");
418 None
419 }
420 1 => Some(T::from_bytes(Cow::Borrowed(rest))),
421 _ => panic!("Invalid Option encoding: unexpected variant marker {last}"),
422 },
423 None => panic!("Invalid Option encoding: expected at least one byte"),
424 }
425 }
426
427 const BOUND: Bound = {
428 match T::BOUND {
429 Bound::Bounded {
430 max_size,
431 is_fixed_size,
432 } => Bound::Bounded {
433 max_size: max_size + 1,
434 is_fixed_size,
435 },
436 Bound::Unbounded => Bound::Unbounded,
437 }
438 };
439}
440
441impl Storable for Principal {
442 fn to_bytes(&self) -> Cow<[u8]> {
443 Cow::Borrowed(self.as_slice())
444 }
445
446 fn from_bytes(bytes: Cow<[u8]>) -> Self {
447 Self::from_slice(&bytes)
448 }
449
450 const BOUND: Bound = Bound::Bounded {
451 max_size: Principal::MAX_LENGTH_IN_BYTES as u32,
452 is_fixed_size: false,
453 };
454}
455
456pub(crate) struct Bounds {
457 pub max_size: u32,
458 pub is_fixed_size: bool,
459}
460
461pub(crate) const fn bounds<A: Storable>() -> Bounds {
463 if let Bound::Bounded {
464 max_size,
465 is_fixed_size,
466 } = A::BOUND
467 {
468 Bounds {
469 max_size,
470 is_fixed_size,
471 }
472 } else {
473 panic!("Cannot get bounds of unbounded type.");
474 }
475}
476
477pub(crate) const fn bytes_to_store_size_bounded(bounds: &Bounds) -> u32 {
478 if bounds.is_fixed_size {
479 0
480 } else {
481 bytes_to_store_size(bounds.max_size as usize) as u32
482 }
483}
484
485const fn bytes_to_store_size(bytes_size: usize) -> usize {
486 if bytes_size <= u8::MAX as usize {
487 1
488 } else if bytes_size <= u16::MAX as usize {
489 2
490 } else {
491 4
492 }
493}