use crate::model::Hash;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum BlobType {
Thought,
Edge,
Commit,
Tree,
}
impl BlobType {
pub fn as_byte(&self) -> u8 {
match self {
BlobType::Thought => 0,
BlobType::Edge => 1,
BlobType::Commit => 2,
BlobType::Tree => 3,
}
}
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(BlobType::Thought),
1 => Some(BlobType::Edge),
2 => Some(BlobType::Commit),
3 => Some(BlobType::Tree),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Blob {
pub blob_type: BlobType,
pub data: Vec<u8>,
}
impl Blob {
pub fn new(blob_type: BlobType, data: Vec<u8>) -> Self {
Blob { blob_type, data }
}
pub fn hash(&self) -> Hash {
Hash::digest_many(&[&[self.blob_type.as_byte()], &self.data])
}
pub fn compress(&self) -> crate::Result<Vec<u8>> {
let mut output = Vec::new();
output.push(self.blob_type.as_byte());
let compressed = zstd::encode_all(self.data.as_slice(), 3)?;
output.extend(compressed);
Ok(output)
}
pub fn decompress(data: &[u8]) -> crate::Result<Self> {
if data.is_empty() {
return Err(crate::Error::Corruption("Empty blob data".into()));
}
let blob_type = BlobType::from_byte(data[0])
.ok_or_else(|| crate::Error::Corruption(format!("Invalid blob type: {}", data[0])))?;
let decompressed = zstd::decode_all(&data[1..])?;
Ok(Blob {
blob_type,
data: decompressed,
})
}
pub fn size(&self) -> usize {
self.data.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blob_roundtrip() {
let original = Blob::new(BlobType::Thought, b"hello world".to_vec());
let compressed = original.compress().unwrap();
let restored = Blob::decompress(&compressed).unwrap();
assert_eq!(original.blob_type, restored.blob_type);
assert_eq!(original.data, restored.data);
}
#[test]
fn test_blob_hash_includes_type() {
let thought = Blob::new(BlobType::Thought, b"data".to_vec());
let edge = Blob::new(BlobType::Edge, b"data".to_vec());
assert_ne!(thought.hash(), edge.hash());
}
}