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)]
16pub enum RawRowError {
17 #[error("row exceeds max size: {len} bytes (limit {MAX_ROW_BYTES})")]
18 TooLarge { len: u32 },
19}
20
21impl RawRowError {
22 #[must_use]
23 pub const fn class(&self) -> ErrorClass {
24 ErrorClass::Unsupported
25 }
26
27 #[must_use]
28 pub const fn origin(&self) -> ErrorOrigin {
29 ErrorOrigin::Store
30 }
31}
32
33impl From<RawRowError> for InternalError {
34 fn from(err: RawRowError) -> Self {
35 Self::new(err.class(), err.origin(), err.to_string())
36 }
37}
38
39#[derive(Debug, ThisError)]
45pub enum RowDecodeError {
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 #[allow(clippy::cast_possible_truncation)]
63 pub fn try_new(bytes: Vec<u8>) -> Result<Self, RawRowError> {
64 let len = bytes.len() as u32;
65 if len > MAX_ROW_BYTES {
66 return Err(RawRowError::TooLarge { len });
67 }
68 Ok(Self(bytes))
69 }
70
71 #[must_use]
72 pub fn as_bytes(&self) -> &[u8] {
73 &self.0
74 }
75
76 #[must_use]
78 pub const fn len(&self) -> usize {
79 self.0.len()
80 }
81
82 #[must_use]
83 pub const fn is_empty(&self) -> bool {
84 self.0.is_empty()
85 }
86
87 pub fn try_decode<E: EntityKind>(&self) -> Result<E, RowDecodeError> {
89 deserialize::<E>(&self.0).map_err(|_| RowDecodeError::Deserialize)
90 }
91}
92
93impl Storable for RawRow {
94 fn to_bytes(&self) -> Cow<'_, [u8]> {
95 Cow::Borrowed(&self.0)
96 }
97
98 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
99 Self(bytes.into_owned())
101 }
102
103 fn into_bytes(self) -> Vec<u8> {
104 self.0
105 }
106
107 const BOUND: Bound = Bound::Bounded {
108 max_size: MAX_ROW_BYTES,
109 is_fixed_size: false,
110 };
111}
112
113#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn raw_row_rejects_oversized_payload() {
123 let bytes = vec![0u8; MAX_ROW_BYTES as usize + 1];
124 let err = RawRow::try_new(bytes).unwrap_err();
125 assert!(matches!(err, RawRowError::TooLarge { .. }));
126 }
127}