use crate::{
db::{
codec::MAX_ROW_BYTES,
data::{
DataKey, PersistedRow, SerializedUpdatePatch, StructuralSlotReader,
apply_serialized_update_patch_to_raw_row, canonical_row_from_entity,
persisted_row::canonical_row_from_complete_serialized_update_patch,
},
},
error::InternalError,
model::entity::EntityModel,
traits::Storable,
};
use canic_cdk::structures::storable::Bound;
use std::{borrow::Cow, ops::Deref};
use thiserror::Error as ThisError;
pub(crate) type DataRow = (DataKey, RawRow);
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum SelectiveRowRead<T> {
MissingRow,
Present(T),
}
impl<T> SelectiveRowRead<T> {
#[must_use]
pub(in crate::db) fn into_present(self) -> Option<T> {
match self {
Self::MissingRow => None,
Self::Present(value) => Some(value),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct CanonicalRow(RawRow);
impl CanonicalRow {
pub(in crate::db::data) const fn from_canonical_raw_row(raw_row: RawRow) -> Self {
Self(raw_row)
}
pub(in crate::db) fn into_raw_row(self) -> RawRow {
self.0
}
#[must_use]
pub(in crate::db) const fn as_raw_row(&self) -> &RawRow {
&self.0
}
pub(in crate::db) fn from_entity<E>(entity: &E) -> Result<Self, InternalError>
where
E: PersistedRow,
{
canonical_row_from_entity(entity)
}
pub(in crate::db) fn from_complete_serialized_update_patch(
model: &'static EntityModel,
patch: &SerializedUpdatePatch,
) -> Result<Self, InternalError> {
canonical_row_from_complete_serialized_update_patch(model, patch)
}
}
impl Deref for CanonicalRow {
type Target = RawRow;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, ThisError)]
pub(crate) enum RawRowError {
#[error("row exceeds max size: {len} bytes (limit {MAX_ROW_BYTES})")]
TooLarge { len: usize },
}
impl From<RawRowError> for InternalError {
fn from(err: RawRowError) -> Self {
Self::store_unsupported(err.to_string())
}
}
#[derive(Debug, ThisError)]
pub(crate) enum RowDecodeError {
#[error("row failed to deserialize: {source}")]
Deserialize {
#[source]
source: InternalError,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RawRow(Vec<u8>);
impl RawRow {
pub(crate) const fn ensure_size(bytes: &[u8]) -> Result<(), RawRowError> {
if bytes.len() > MAX_ROW_BYTES as usize {
return Err(RawRowError::TooLarge { len: bytes.len() });
}
Ok(())
}
pub(in crate::db) fn from_untrusted_bytes(bytes: Vec<u8>) -> Result<Self, RawRowError> {
Self::ensure_size(&bytes)?;
Ok(Self(bytes))
}
#[cfg(test)]
pub(crate) fn try_new(bytes: Vec<u8>) -> Result<Self, RawRowError> {
Self::from_untrusted_bytes(bytes)
}
pub(in crate::db) fn from_complete_serialized_update_patch(
model: &'static EntityModel,
patch: &SerializedUpdatePatch,
) -> Result<CanonicalRow, InternalError> {
CanonicalRow::from_complete_serialized_update_patch(model, patch)
}
pub(in crate::db) fn apply_serialized_update_patch(
&self,
model: &'static EntityModel,
patch: &SerializedUpdatePatch,
) -> Result<CanonicalRow, InternalError> {
apply_serialized_update_patch_to_raw_row(model, self, patch)
}
#[must_use]
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.0
}
#[must_use]
pub(crate) const fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub(crate) fn try_decode<E: PersistedRow>(&self) -> Result<E, RowDecodeError> {
let mut slots = StructuralSlotReader::from_raw_row(self, E::MODEL)
.map_err(|source| RowDecodeError::Deserialize { source })?;
E::materialize_from_slots(&mut slots)
.map_err(|source| RowDecodeError::Deserialize { source })
}
}
impl Storable for RawRow {
fn to_bytes(&self) -> Cow<'_, [u8]> {
Cow::Borrowed(&self.0)
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
Self(bytes.into_owned())
}
fn into_bytes(self) -> Vec<u8> {
self.0
}
const BOUND: Bound = Bound::Bounded {
max_size: MAX_ROW_BYTES,
is_fixed_size: false,
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{ErrorClass, ErrorOrigin};
#[test]
fn raw_row_rejects_oversized_payload() {
let bytes = vec![0u8; MAX_ROW_BYTES as usize + 1];
let err = RawRow::try_new(bytes).unwrap_err();
assert!(matches!(err, RawRowError::TooLarge { .. }));
}
#[test]
fn raw_row_error_maps_to_store_unsupported() {
let err: InternalError = RawRowError::TooLarge {
len: MAX_ROW_BYTES as usize + 1,
}
.into();
assert_eq!(err.class, ErrorClass::Unsupported);
assert_eq!(err.origin, ErrorOrigin::Store);
}
}