//! Strict bounded Postcard structure checks shared by schema-record persistence and tests. use std::fmt; use serde::Serialize; use serde::de::DeserializeOwned; use crate::records::SchemaType; pub const MAX_SCHEMA_RECORD_BYTES: usize = 1024 * 1024; #[derive(Debug)] pub enum CodecError { RecordTooLarge { length: usize, maximum: usize }, TrailingPayload, NonMinimalPayload, MalformedPayload(postcard::Error), } impl fmt::Display for CodecError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::RecordTooLarge { length, maximum } => { write!(formatter, "schema record length {length} exceeds {maximum}") } Self::TrailingPayload => { formatter.write_str("schema record has trailing payload bytes") } Self::NonMinimalPayload => formatter .write_str("schema record does not use the minimal Postcard representation"), Self::MalformedPayload(error) => write!(formatter, "malformed schema payload: {error}"), } } } impl std::error::Error for CodecError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::MalformedPayload(error) => Some(error), _ => None, } } } /// Encodes one schema value using Postcard's minimal representation. /// /// # Errors /// /// Returns an error when serialization fails or the encoded value exceeds the generic limit. pub fn encode(record: &T) -> Result, CodecError> { let encoded = postcard::to_stdvec(record).map_err(CodecError::MalformedPayload)?; if encoded.len() > MAX_SCHEMA_RECORD_BYTES { return Err(CodecError::RecordTooLarge { length: encoded.len(), maximum: MAX_SCHEMA_RECORD_BYTES, }); } Ok(encoded) } /// Decodes one complete, minimally encoded schema value after enforcing the generic size limit. /// /// # Errors /// /// Returns an error for oversized, malformed, trailing, or non-minimal Postcard input. pub fn decode(encoded: &[u8]) -> Result where T: DeserializeOwned + SchemaType + Serialize, { if encoded.len() > MAX_SCHEMA_RECORD_BYTES { return Err(CodecError::RecordTooLarge { length: encoded.len(), maximum: MAX_SCHEMA_RECORD_BYTES, }); } let (record, remainder) = postcard::take_from_bytes(encoded).map_err(CodecError::MalformedPayload)?; if !remainder.is_empty() { return Err(CodecError::TrailingPayload); } let minimal = postcard::to_stdvec(&record).map_err(CodecError::MalformedPayload)?; if minimal != encoded { return Err(CodecError::NonMinimalPayload); } Ok(record) }