icydb_core/db/store/
row.rs1use crate::{
2 error::{ErrorClass, ErrorOrigin, InternalError},
3 serialize::{SerializeError, 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() -> ErrorClass {
24 ErrorClass::Unsupported
25 }
26
27 #[must_use]
28 pub const fn origin() -> ErrorOrigin {
29 ErrorOrigin::Store
30 }
31}
32
33impl From<RawRowError> for InternalError {
34 fn from(err: RawRowError) -> Self {
35 Self::new(RawRowError::class(), RawRowError::origin(), err.to_string())
36 }
37}
38
39#[derive(Debug, ThisError)]
45pub enum RowDecodeError {
46 #[error("row failed to deserialize: {source}")]
47 Deserialize {
48 #[source]
49 source: SerializeError,
50 },
51}
52
53pub const MAX_ROW_BYTES: u32 = 4 * 1024 * 1024;
59
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct RawRow(Vec<u8>);
62
63impl RawRow {
64 #[expect(clippy::cast_possible_truncation)]
66 pub fn try_new(bytes: Vec<u8>) -> Result<Self, RawRowError> {
67 let len = bytes.len() as u32;
68 if len > MAX_ROW_BYTES {
69 return Err(RawRowError::TooLarge { len });
70 }
71 Ok(Self(bytes))
72 }
73
74 #[must_use]
75 pub fn as_bytes(&self) -> &[u8] {
76 &self.0
77 }
78
79 #[must_use]
81 pub const fn len(&self) -> usize {
82 self.0.len()
83 }
84
85 #[must_use]
86 pub const fn is_empty(&self) -> bool {
87 self.0.is_empty()
88 }
89
90 pub fn try_decode<E: EntityKind>(&self) -> Result<E, RowDecodeError> {
92 deserialize::<E>(&self.0).map_err(|source| RowDecodeError::Deserialize { source })
93 }
94}
95
96impl Storable for RawRow {
97 fn to_bytes(&self) -> Cow<'_, [u8]> {
98 Cow::Borrowed(&self.0)
99 }
100
101 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
102 Self(bytes.into_owned())
104 }
105
106 fn into_bytes(self) -> Vec<u8> {
107 self.0
108 }
109
110 const BOUND: Bound = Bound::Bounded {
111 max_size: MAX_ROW_BYTES,
112 is_fixed_size: false,
113 };
114}
115
116#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn raw_row_rejects_oversized_payload() {
126 let bytes = vec![0u8; MAX_ROW_BYTES as usize + 1];
127 let err = RawRow::try_new(bytes).unwrap_err();
128 assert!(matches!(err, RawRowError::TooLarge { .. }));
129 }
130}