icydb_core/db/store/
row.rs

1use crate::{
2    error::{ErrorClass, ErrorOrigin, InternalError},
3    serialize::deserialize,
4    traits::{EntityKind, Storable},
5};
6use canic_cdk::structures::storable::Bound;
7use std::borrow::Cow;
8use thiserror::Error as ThisError;
9
10///
11/// RawRowError
12///
13
14#[derive(Debug, ThisError)]
15pub enum RawRowError {
16    #[error("row exceeds max size: {len} bytes (limit {MAX_ROW_BYTES})")]
17    TooLarge { len: usize },
18}
19
20impl RawRowError {
21    #[must_use]
22    pub const fn class(&self) -> ErrorClass {
23        ErrorClass::Unsupported
24    }
25
26    #[must_use]
27    pub const fn origin(&self) -> ErrorOrigin {
28        ErrorOrigin::Store
29    }
30}
31
32impl From<RawRowError> for InternalError {
33    fn from(err: RawRowError) -> Self {
34        Self::new(err.class(), err.origin(), err.to_string())
35    }
36}
37
38///
39/// RawDecodeError
40///
41
42#[derive(Debug, ThisError)]
43pub enum RowDecodeError {
44    #[error("row exceeds max size: {len} bytes (limit {MAX_ROW_BYTES})")]
45    TooLarge { len: usize },
46    #[error("row failed to deserialize")]
47    Deserialize,
48}
49
50///
51/// RawRow
52///
53
54/// Max serialized bytes for a single row to keep value loads bounded.
55pub const MAX_ROW_BYTES: u32 = 4 * 1024 * 1024;
56
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct RawRow(Vec<u8>);
59
60impl RawRow {
61    pub fn try_new(bytes: Vec<u8>) -> Result<Self, RawRowError> {
62        if bytes.len() > MAX_ROW_BYTES as usize {
63            return Err(RawRowError::TooLarge { len: bytes.len() });
64        }
65        Ok(Self(bytes))
66    }
67
68    #[must_use]
69    pub fn as_bytes(&self) -> &[u8] {
70        &self.0
71    }
72
73    #[must_use]
74    pub const fn len(&self) -> usize {
75        self.0.len()
76    }
77
78    #[must_use]
79    pub const fn is_empty(&self) -> bool {
80        self.0.is_empty()
81    }
82
83    pub fn try_decode<E: EntityKind>(&self) -> Result<E, RowDecodeError> {
84        if self.0.len() > MAX_ROW_BYTES as usize {
85            return Err(RowDecodeError::TooLarge { len: self.0.len() });
86        }
87
88        deserialize::<E>(&self.0).map_err(|_| RowDecodeError::Deserialize)
89    }
90}
91
92impl Storable for RawRow {
93    fn to_bytes(&self) -> Cow<'_, [u8]> {
94        Cow::Borrowed(&self.0)
95    }
96
97    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
98        Self(bytes.into_owned())
99    }
100
101    fn into_bytes(self) -> Vec<u8> {
102        self.0
103    }
104
105    const BOUND: Bound = Bound::Bounded {
106        max_size: MAX_ROW_BYTES,
107        is_fixed_size: false,
108    };
109}
110
111///
112/// TESTS
113///
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn raw_row_rejects_oversized_payload() {
121        let bytes = vec![0u8; MAX_ROW_BYTES as usize + 1];
122        let err = RawRow::try_new(bytes).unwrap_err();
123        assert!(matches!(err, RawRowError::TooLarge { .. }));
124    }
125}