85 lines
2.7 KiB
Rust
85 lines
2.7 KiB
Rust
|
|
//! 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)
|
||
|
|
}
|