icydb_core/db/store/
row.rs1use 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#[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#[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
50pub 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#[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}