use postcard::{from_bytes, to_stdvec};
use serde::{Serialize, de::DeserializeOwned};
use zstd::{decode_all, encode_all};
use crate::error::{DecodeError, EncodeError};
pub fn encode<T: Serialize + ?Sized>(value: &T, level: i32) -> Result<Vec<u8>, EncodeError> {
let raw = to_stdvec(value).map_err(|e| EncodeError::Serialization(e.to_string()))?;
encode_all(&raw[..], level).map_err(|e| EncodeError::Compression(e.to_string()))
}
pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, DecodeError> {
let raw = decode_all(bytes).map_err(|e| DecodeError::Decompression(e.to_string()))?;
from_bytes(&raw).map_err(|e| DecodeError::Deserialization(e.to_string()))
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Sample {
version: u64,
rows: Vec<String>,
}
fn sample() -> Sample {
Sample {
version: 42,
rows: vec!["alpha".to_string(), "beta".to_string()],
}
}
#[test]
fn round_trips_a_single_value() {
let encoded = encode(&sample(), 1).unwrap();
assert_eq!(decode::<Sample>(&encoded).unwrap(), sample());
}
#[test]
fn round_trips_a_batch_through_the_same_functions() {
let batch = vec![sample(), sample()];
let encoded = encode(&batch, 1).unwrap();
assert_eq!(decode::<Vec<Sample>>(&encoded).unwrap(), batch);
}
#[test]
fn encodes_an_unsized_slice_the_same_as_its_vec() {
let batch = vec![sample(), sample()];
assert_eq!(encode(&batch[..], 1).unwrap(), encode(&batch, 1).unwrap());
}
#[test]
fn every_level_decodes_with_the_same_reader() {
for level in [1, 2, 3, 9] {
let encoded = encode(&sample(), level).unwrap();
assert_eq!(decode::<Sample>(&encoded).unwrap(), sample(), "level {level}");
}
}
#[test]
fn output_is_compressed_not_raw_postcard() {
let big = Sample {
version: 1,
rows: vec!["x".repeat(4096)],
};
let raw = to_stdvec(&big).unwrap();
assert!(encode(&big, 1).unwrap().len() < raw.len() / 4);
}
#[test]
fn rejects_bytes_that_are_not_compressed() {
let raw = to_stdvec(&sample()).unwrap();
assert!(matches!(decode::<Sample>(&raw), Err(DecodeError::Decompression(_))));
}
#[test]
fn rejects_a_payload_that_decompresses_to_the_wrong_type() {
let encoded = encode(&"not a sample".to_string(), 1).unwrap();
assert!(matches!(decode::<Sample>(&encoded), Err(DecodeError::Deserialization(_))));
}
#[test]
fn rejects_truncated_input() {
let encoded = encode(&sample(), 1).unwrap();
let truncated = &encoded[..encoded.len() / 2];
assert!(decode::<Sample>(truncated).is_err());
}
}