lazaret/crates/lazaret-core/src/codec.rs

85 lines
2.7 KiB
Rust
Raw Normal View History

//! Strict bounded Postcard structure checks shared by schema-record persistence and tests.
use std::fmt;
use serde::Serialize;
use serde::de::DeserializeOwned;
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<T: Serialize>(record: &T) -> Result<Vec<u8>, 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<T>(encoded: &[u8]) -> Result<T, CodecError>
where
T: DeserializeOwned + 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)
}