use crate::types::{value::Row, MeruError, Result};
const POSTCARD_MARKER: u8 = 0x01;
#[inline]
pub fn encode_row(row: &Row) -> Result<Vec<u8>> {
let raw = postcard::to_allocvec(row)
.map_err(|e| MeruError::InvalidArgument(format!("row postcard serialize failed: {e}")))?;
let mut out = Vec::with_capacity(1 + raw.len());
out.push(POSTCARD_MARKER);
out.extend_from_slice(&raw);
Ok(out)
}
#[inline]
pub fn decode_row(bytes: &[u8]) -> Result<Row> {
if bytes.is_empty() {
return Ok(Row::default());
}
if bytes[0] == POSTCARD_MARKER {
postcard::from_bytes(&bytes[1..]).map_err(|e| {
MeruError::Corruption(format!(
"row postcard decode failed ({} bytes after marker): {e}",
bytes.len() - 1
))
})
} else {
serde_json::from_slice(bytes).map_err(|e| {
MeruError::Corruption(format!(
"row legacy-JSON decode failed ({} bytes): {e}",
bytes.len()
))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::value::FieldValue;
use bytes::Bytes;
#[test]
fn roundtrip_postcard() {
let row = Row::new(vec![
Some(FieldValue::Int64(42)),
None,
Some(FieldValue::Bytes(Bytes::from("hello"))),
]);
let encoded = encode_row(&row).unwrap();
assert_eq!(encoded[0], POSTCARD_MARKER);
let decoded = decode_row(&encoded).unwrap();
assert_eq!(decoded, row);
}
#[test]
fn decode_legacy_json() {
let row = Row::new(vec![
Some(FieldValue::Int64(1)),
Some(FieldValue::Boolean(true)),
]);
let json = serde_json::to_vec(&row).unwrap();
assert_ne!(json[0], POSTCARD_MARKER);
let decoded = decode_row(&json).unwrap();
assert_eq!(decoded, row);
}
#[test]
fn decode_empty() {
let decoded = decode_row(&[]).unwrap();
assert_eq!(decoded, Row::default());
}
#[test]
fn decode_corrupt_postcard_returns_corruption_error() {
let bad = [POSTCARD_MARKER, 0xFF, 0xFF, 0xFF, 0xFF];
let err = decode_row(&bad).unwrap_err();
match err {
MeruError::Corruption(msg) => {
assert!(
msg.contains("postcard decode failed"),
"error message should identify postcard decode failure; got: {msg}"
);
}
other => panic!("expected Corruption, got {other:?}"),
}
}
#[test]
fn decode_corrupt_legacy_json_returns_corruption_error() {
let bad = b"{not valid json}";
let err = decode_row(bad).unwrap_err();
match err {
MeruError::Corruption(msg) => {
assert!(
msg.contains("legacy-JSON decode failed"),
"error should identify JSON decode failure; got: {msg}"
);
}
other => panic!("expected Corruption, got {other:?}"),
}
}
#[test]
fn postcard_smaller_than_json() {
let row = Row::new(vec![
Some(FieldValue::Int64(123456789)),
Some(FieldValue::Double(98.765)),
Some(FieldValue::Bytes(Bytes::from("test data"))),
None,
]);
let postcard_bytes = encode_row(&row).unwrap();
let json_bytes = serde_json::to_vec(&row).unwrap();
assert!(
postcard_bytes.len() < json_bytes.len(),
"postcard={} json={}",
postcard_bytes.len(),
json_bytes.len()
);
}
}