use super::types::SchemaError;
use crate::features::compression::{CompressionAlgorithm, compress, decompress};
const ALGO_NONE: u8 = 0x00;
const ALGO_BROTLI: u8 = 0x01;
const ALGO_LZ4: u8 = 0x02;
const ALGO_ZSTD: u8 = 0x03;
const DEFAULT_LEVEL: u32 = 6;
#[derive(Clone, Copy, Debug)]
pub enum SchemaCompressionAlgo {
Brotli,
Lz4,
Zstd,
}
pub fn compress_with_prefix(
binary: &[u8],
algo: Option<SchemaCompressionAlgo>,
) -> Result<Vec<u8>, SchemaError> {
let (algo_byte, compressed) = match algo {
None => (ALGO_NONE, binary.to_vec()),
Some(SchemaCompressionAlgo::Brotli) => {
let c = compress(binary, CompressionAlgorithm::Brotli, DEFAULT_LEVEL)
.map_err(|e| SchemaError::Compression(e.to_string()))?;
(ALGO_BROTLI, c)
}
Some(SchemaCompressionAlgo::Lz4) => {
let c = compress(binary, CompressionAlgorithm::Lz4, DEFAULT_LEVEL)
.map_err(|e| SchemaError::Compression(e.to_string()))?;
(ALGO_LZ4, c)
}
Some(SchemaCompressionAlgo::Zstd) => {
let c = compress(binary, CompressionAlgorithm::Zstd, DEFAULT_LEVEL)
.map_err(|e| SchemaError::Compression(e.to_string()))?;
(ALGO_ZSTD, c)
}
};
let mut result = Vec::with_capacity(1 + compressed.len());
result.push(algo_byte);
result.extend_from_slice(&compressed);
Ok(result)
}
pub fn decompress_with_prefix(binary: &[u8]) -> Result<Vec<u8>, SchemaError> {
if binary.is_empty() {
return Err(SchemaError::UnexpectedEndOfData {
context: "compression prefix".to_string(),
position: 0,
});
}
let algo_byte = binary[0];
let payload = &binary[1..];
match algo_byte {
ALGO_NONE => Ok(payload.to_vec()),
ALGO_BROTLI => decompress(payload, CompressionAlgorithm::Brotli)
.map_err(|e| SchemaError::Decompression(e.to_string())),
ALGO_LZ4 => decompress(payload, CompressionAlgorithm::Lz4)
.map_err(|e| SchemaError::Decompression(e.to_string())),
ALGO_ZSTD => decompress(payload, CompressionAlgorithm::Zstd)
.map_err(|e| SchemaError::Decompression(e.to_string())),
_ => Err(SchemaError::InvalidCompressionAlgorithm(algo_byte)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_compression() {
let data = b"Hello, world!";
let compressed = compress_with_prefix(data, None).unwrap();
assert_eq!(compressed[0], ALGO_NONE);
assert_eq!(&compressed[1..], data);
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_brotli_roundtrip() {
let data = b"Hello, world! This is a test of brotli compression in schema encoding.";
let compressed = compress_with_prefix(data, Some(SchemaCompressionAlgo::Brotli)).unwrap();
assert_eq!(compressed[0], ALGO_BROTLI);
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_lz4_roundtrip() {
let data = b"Hello, world! This is a test of lz4 compression in schema encoding.";
let compressed = compress_with_prefix(data, Some(SchemaCompressionAlgo::Lz4)).unwrap();
assert_eq!(compressed[0], ALGO_LZ4);
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_zstd_roundtrip() {
let data = b"Hello, world! This is a test of zstd compression in schema encoding.";
let compressed = compress_with_prefix(data, Some(SchemaCompressionAlgo::Zstd)).unwrap();
assert_eq!(compressed[0], ALGO_ZSTD);
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_small_payload() {
let data = b"Hi";
let compressed = compress_with_prefix(data, Some(SchemaCompressionAlgo::Brotli)).unwrap();
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_empty_payload() {
let data = b"";
let compressed = compress_with_prefix(data, None).unwrap();
assert_eq!(compressed, vec![ALGO_NONE]);
let decompressed = decompress_with_prefix(&compressed).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_invalid_algorithm() {
let invalid = vec![0xFF, 0x01, 0x02, 0x03];
let result = decompress_with_prefix(&invalid);
assert!(matches!(
result,
Err(SchemaError::InvalidCompressionAlgorithm(0xFF))
));
}
#[test]
fn test_missing_prefix() {
let result = decompress_with_prefix(&[]);
assert!(matches!(
result,
Err(SchemaError::UnexpectedEndOfData { .. })
));
}
}