diff --git a/src/pcd.rs b/src/pcd.rs index 0120a4b..4df8a44 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -27,6 +27,112 @@ use crate::{ pub const PCD_VERSION: u8 = 1; pub(crate) const ZSTD_COMPRESSION_LEVEL: i32 = zstd::DEFAULT_COMPRESSION_LEVEL; +pub trait PcdSerializable { + fn serialize_to_pcd(&self) -> Result>; + fn deserialize_from_pcd(data: &[u8]) -> Result where Self: Sized; +} + +impl PcdSerializable for serde_json::Value { + fn serialize_to_pcd(&self) -> Result> { + let mut compressed = Vec::new(); + let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; + + encoder.write_all(&[PCD_VERSION])?; + encoder.write_all(&[DataType::Json as u8])?; + serde_json::to_writer(&mut encoder, self)?; + encoder.finish()?; + + Ok(compressed) + } + + fn deserialize_from_pcd(data: &[u8]) -> Result { + let mut decompressed = Vec::new(); + zstd::stream::copy_decode(data, &mut decompressed)?; + + if decompressed.len() < 3 { + return Err(Error::msg("Invalid data: too short")); + } + + let version = decompressed[0]; + let data_type = DataType::try_from(decompressed[1])?; + + match (version, data_type) { + (PCD_VERSION, DataType::Json) => { + let json_bytes = &decompressed[2..]; + let json_string = String::from_utf8(json_bytes.to_vec())?; + Ok(serde_json::from_str(&json_string)?) + }, + _ => Err(Error::msg("Invalid version or data type")) + } + } +} + +impl PcdSerializable for FileBlob { + fn serialize_to_pcd(&self) -> Result> { + let mut compressed = Vec::new(); + let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; + + encoder.write_all(&[PCD_VERSION])?; + encoder.write_all(&[DataType::FileBlob as u8])?; + + let type_len = self.r#type.as_bytes().len() as u8; + encoder.write_all(&[type_len])?; + encoder.write_all(self.r#type.as_bytes())?; + encoder.write_all(&self.data)?; + + encoder.finish()?; + Ok(compressed) + } + + fn deserialize_from_pcd(data: &[u8]) -> Result { + let mut decompressed = Vec::new(); + zstd::stream::copy_decode(data, &mut decompressed)?; + + if decompressed.len() < 4 { + return Err(Error::msg("Invalid data: too short")); + } + + let version = decompressed[0]; + let data_type = DataType::try_from(decompressed[1])?; + + match (version, data_type) { + (PCD_VERSION, DataType::FileBlob) => { + let type_len = decompressed[2] as usize; + let type_str = String::from_utf8(decompressed[3..3+type_len].to_vec())?; + let data = decompressed[3+type_len..].to_vec(); + + Ok(FileBlob { r#type: type_str, data }) + }, + _ => Err(Error::msg("Invalid version or data type")) + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DataType { + FileBlob = 0, + Json = 1, +} + +impl TryFrom for DataType { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(DataType::FileBlob), + 1 => Ok(DataType::Json), + _ => return Err(Error::msg(format!("Unknown data type: {}", value))), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct FileBlob { + pub r#type: String, + pub data: Vec, +} + #[derive(Debug, Default, Clone, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Member { @@ -110,12 +216,6 @@ impl Member { } } -#[derive(Serialize, Deserialize)] -pub struct FileBlob { - pub r#type: String, - pub data: Vec, -} - #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Pcd(BTreeMap>);