Add typed schema records and strict version checks
This commit is contained in:
parent
b438dd5201
commit
16defc768a
1 changed files with 3164 additions and 1 deletions
85
crates/lazaret-core/src/codec.rs
Normal file
85
crates/lazaret-core/src/codec.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//! 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)
|
||||
}
|
||||
Loading…
Reference in a new issue