use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::store::MetadataStore;
use super::types::MetadataValue;
#[derive(Serialize, Deserialize)]
enum PostcardValue {
S(String),
I(i64),
F(f64),
B(bool),
A(Vec<String>),
}
impl From<&MetadataValue> for PostcardValue {
fn from(value: &MetadataValue) -> Self {
match value {
MetadataValue::String(s) => PostcardValue::S(s.clone()),
MetadataValue::Integer(i) => PostcardValue::I(*i),
MetadataValue::Float(f) => PostcardValue::F(*f),
MetadataValue::Boolean(b) => PostcardValue::B(*b),
MetadataValue::StringArray(a) => PostcardValue::A(a.clone()),
}
}
}
impl From<PostcardValue> for MetadataValue {
fn from(pv: PostcardValue) -> Self {
match pv {
PostcardValue::S(s) => MetadataValue::String(s),
PostcardValue::I(i) => MetadataValue::Integer(i),
PostcardValue::F(f) => MetadataValue::Float(f),
PostcardValue::B(b) => MetadataValue::Boolean(b),
PostcardValue::A(a) => MetadataValue::StringArray(a),
}
}
}
#[derive(Serialize, Deserialize)]
struct PostcardMetadata {
entries: Vec<(u32, String, PostcardValue)>,
}
impl From<&MetadataStore> for PostcardMetadata {
fn from(store: &MetadataStore) -> Self {
let mut entries = Vec::new();
for vector_id in store.vector_ids() {
if let Some(metadata) = store.get_all(*vector_id) {
for (key, value) in metadata {
entries.push((*vector_id, key.clone(), PostcardValue::from(value)));
}
}
}
Self { entries }
}
}
impl From<PostcardMetadata> for MetadataStore {
fn from(pm: PostcardMetadata) -> Self {
let mut store = MetadataStore::new();
for (vector_id, key, pv) in pm.entries {
let value = MetadataValue::from(pv);
let _ = store.insert(vector_id, &key, value);
}
store
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum SerializationError {
#[error("postcard encode failed: {0}")]
PostcardEncode(String),
#[error("postcard decode failed: {0}")]
PostcardDecode(String),
#[error("json encode failed: {0}")]
JsonEncode(String),
#[error("json decode failed: {0}")]
JsonDecode(String),
#[error("CRC mismatch: expected {expected:#x}, got {actual:#x}")]
CrcMismatch {
expected: u32,
actual: u32,
},
}
impl MetadataStore {
pub fn to_postcard(&self) -> Result<Vec<u8>, SerializationError> {
let pm = PostcardMetadata::from(self);
postcard::to_allocvec(&pm).map_err(|e| SerializationError::PostcardEncode(e.to_string()))
}
pub fn from_postcard(bytes: &[u8]) -> Result<Self, SerializationError> {
let pm: PostcardMetadata = postcard::from_bytes(bytes)
.map_err(|e| SerializationError::PostcardDecode(e.to_string()))?;
Ok(MetadataStore::from(pm))
}
pub fn to_json(&self) -> Result<Vec<u8>, SerializationError> {
serde_json::to_vec(self).map_err(|e| SerializationError::JsonEncode(e.to_string()))
}
pub fn from_json(bytes: &[u8]) -> Result<Self, SerializationError> {
serde_json::from_slice(bytes).map_err(|e| SerializationError::JsonDecode(e.to_string()))
}
#[must_use]
pub fn calculate_crc(bytes: &[u8]) -> u32 {
crc32fast::hash(bytes)
}
pub fn verify_crc(bytes: &[u8], expected: u32) -> Result<(), SerializationError> {
let actual = Self::calculate_crc(bytes);
if actual != expected {
return Err(SerializationError::CrcMismatch { expected, actual });
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::MetadataValue;
#[test]
fn test_postcard_roundtrip_empty() {
let store = MetadataStore::new();
let bytes = store.to_postcard().unwrap();
let restored = MetadataStore::from_postcard(&bytes).unwrap();
assert!(restored.is_empty());
}
#[test]
fn test_postcard_roundtrip_with_data() {
let mut store = MetadataStore::new();
store
.insert(1, "key", MetadataValue::String("value".into()))
.unwrap();
store
.insert(1, "count", MetadataValue::Integer(42))
.unwrap();
store
.insert(2, "price", MetadataValue::Float(29.99))
.unwrap();
let bytes = store.to_postcard().unwrap();
let restored = MetadataStore::from_postcard(&bytes).unwrap();
assert_eq!(store.get(1, "key"), restored.get(1, "key"));
assert_eq!(store.get(1, "count"), restored.get(1, "count"));
assert_eq!(store.get(2, "price"), restored.get(2, "price"));
assert_eq!(store, restored);
}
#[test]
fn test_postcard_roundtrip_all_types() {
let mut store = MetadataStore::new();
store
.insert(0, "string", MetadataValue::String("hello".into()))
.unwrap();
store
.insert(0, "integer", MetadataValue::Integer(-42))
.unwrap();
store
.insert(0, "float", MetadataValue::Float(core::f64::consts::PI))
.unwrap();
store
.insert(0, "boolean", MetadataValue::Boolean(true))
.unwrap();
store
.insert(
0,
"array",
MetadataValue::StringArray(vec!["a".into(), "b".into(), "c".into()]),
)
.unwrap();
let bytes = store.to_postcard().unwrap();
let restored = MetadataStore::from_postcard(&bytes).unwrap();
assert_eq!(store, restored);
}
#[test]
fn test_postcard_decode_invalid_bytes() {
let invalid_bytes = [0xFF, 0xFF, 0xFF, 0xFF];
let result = MetadataStore::from_postcard(&invalid_bytes);
assert!(result.is_err());
match result {
Err(SerializationError::PostcardDecode(_)) => {}
_ => panic!("Expected PostcardDecode error"),
}
}
#[test]
fn test_postcard_is_compact() {
let mut store = MetadataStore::new();
store
.insert(0, "key", MetadataValue::String("value".into()))
.unwrap();
let postcard_bytes = store.to_postcard().unwrap();
let json_bytes = store.to_json().unwrap();
assert!(
postcard_bytes.len() < json_bytes.len(),
"Postcard ({} bytes) should be smaller than JSON ({} bytes)",
postcard_bytes.len(),
json_bytes.len()
);
}
#[test]
fn test_json_roundtrip_empty() {
let store = MetadataStore::new();
let bytes = store.to_json().unwrap();
let restored = MetadataStore::from_json(&bytes).unwrap();
assert!(restored.is_empty());
}
#[test]
fn test_json_roundtrip_with_data() {
let mut store = MetadataStore::new();
store
.insert(1, "key", MetadataValue::String("value".into()))
.unwrap();
let bytes = store.to_json().unwrap();
let restored = MetadataStore::from_json(&bytes).unwrap();
assert_eq!(store.get(1, "key"), restored.get(1, "key"));
}
#[test]
fn test_json_is_human_readable() {
let mut store = MetadataStore::new();
store
.insert(0, "title", MetadataValue::String("Hello World".into()))
.unwrap();
let bytes = store.to_json().unwrap();
let json_str = String::from_utf8(bytes).unwrap();
assert!(json_str.contains("title"));
assert!(json_str.contains("Hello World"));
}
#[test]
fn test_json_decode_invalid_bytes() {
let invalid_bytes = b"not valid json";
let result = MetadataStore::from_json(invalid_bytes);
assert!(result.is_err());
match result {
Err(SerializationError::JsonDecode(_)) => {}
_ => panic!("Expected JsonDecode error"),
}
}
#[test]
fn test_crc_calculates_correctly() {
let store = MetadataStore::new();
let bytes = store.to_postcard().unwrap();
let crc = MetadataStore::calculate_crc(&bytes);
assert!(!bytes.is_empty());
let crc2 = MetadataStore::calculate_crc(&bytes);
assert_eq!(crc, crc2);
}
#[test]
fn test_crc_validates_correctly() {
let store = MetadataStore::new();
let bytes = store.to_postcard().unwrap();
let crc = MetadataStore::calculate_crc(&bytes);
assert!(MetadataStore::verify_crc(&bytes, crc).is_ok());
}
#[test]
fn test_crc_rejects_mismatch() {
let store = MetadataStore::new();
let bytes = store.to_postcard().unwrap();
let crc = MetadataStore::calculate_crc(&bytes);
let result = MetadataStore::verify_crc(&bytes, crc + 1);
assert!(result.is_err());
match result {
Err(SerializationError::CrcMismatch { expected, actual }) => {
assert_eq!(expected, crc + 1);
assert_eq!(actual, crc);
}
_ => panic!("Expected CrcMismatch error"),
}
}
#[test]
fn test_crc_detects_corruption() {
let mut store = MetadataStore::new();
store
.insert(0, "key", MetadataValue::String("value".into()))
.unwrap();
let mut bytes = store.to_postcard().unwrap();
let original_crc = MetadataStore::calculate_crc(&bytes);
if !bytes.is_empty() {
bytes[0] ^= 0xFF;
}
let corrupted_crc = MetadataStore::calculate_crc(&bytes);
assert_ne!(original_crc, corrupted_crc);
assert!(MetadataStore::verify_crc(&bytes, original_crc).is_err());
}
#[test]
fn test_formats_produce_same_data() {
let mut store = MetadataStore::new();
store
.insert(0, "key", MetadataValue::String("value".into()))
.unwrap();
store
.insert(0, "count", MetadataValue::Integer(42))
.unwrap();
let postcard_bytes = store.to_postcard().unwrap();
let json_bytes = store.to_json().unwrap();
let from_postcard = MetadataStore::from_postcard(&postcard_bytes).unwrap();
let from_json = MetadataStore::from_json(&json_bytes).unwrap();
assert_eq!(from_postcard, from_json);
assert_eq!(from_postcard, store);
}
#[test]
fn test_postcard_large_store() {
let mut store = MetadataStore::new();
for v in 0..100u32 {
for k in 0..10 {
store
.insert(
v,
&format!("key_{k}"),
MetadataValue::Integer(i64::from(v) * 10 + i64::from(k)),
)
.unwrap();
}
}
assert_eq!(store.vector_count(), 100);
assert_eq!(store.total_key_count(), 1000);
let bytes = store.to_postcard().unwrap();
let restored = MetadataStore::from_postcard(&bytes).unwrap();
assert_eq!(store, restored);
assert_eq!(restored.vector_count(), 100);
assert_eq!(restored.total_key_count(), 1000);
}
}