use crate::error::{CompressionError, CompressionResult};
use crate::{CompressionLevel, Compressor};
use std::fmt;
use std::io::Write;
#[derive(Clone)]
pub struct BrotliCompressor {
level: CompressionLevel,
}
impl BrotliCompressor {
pub fn new(level: CompressionLevel) -> Self {
BrotliCompressor { level }
}
pub fn fast() -> Self {
BrotliCompressor::new(CompressionLevel::Fast)
}
pub fn default_level() -> Self {
BrotliCompressor::new(CompressionLevel::Default)
}
pub fn best() -> Self {
BrotliCompressor::new(CompressionLevel::Best)
}
}
impl fmt::Debug for BrotliCompressor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BrotliCompressor")
.field("level", &self.level)
.finish()
}
}
impl Compressor for BrotliCompressor {
fn compress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
let level = self.level.to_brotli_level();
let mut output = Vec::with_capacity(data.len() / 2);
output.extend_from_slice(b"BRT\x01");
{
let mut compressor = brotli::CompressorWriter::new(
&mut output,
4096, level,
22, );
if let Err(e) = compressor.write_all(data) {
return Err(CompressionError::brotli_error(format!(
"brotli compression failed: {}",
e
)));
}
if let Err(e) = compressor.flush() {
return Err(CompressionError::brotli_error(format!(
"brotli flush failed: {}",
e
)));
}
}
Ok(output)
}
fn decompress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
if data.len() >= 4 && data.starts_with(b"BRT\x01") {
let compressed_data = &data[4..];
let mut output = Vec::with_capacity(data.len() * 2);
match brotli::BrotliDecompress(&mut std::io::Cursor::new(compressed_data), &mut output)
{
Ok(_) => Ok(output),
Err(e) => Err(CompressionError::decompression_failed(format!(
"brotli decompression failed: {}",
e
))),
}
} else {
Ok(data.to_vec())
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_brotli_compress_decompress() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let original = b"Hello, World! This is a test of brotli compression.";
let compressed = compressor.compress(original).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(original, &decompressed[..]);
}
#[test]
fn test_brotli_has_marker() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let original = b"Test data for brotli";
let compressed = compressor.compress(original).unwrap();
assert!(compressed.starts_with(b"BRT\x01"));
}
#[test]
fn test_brotli_compress_empty() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let compressed = compressor.compress(b"").unwrap();
assert_eq!(compressed.len(), 0);
}
#[test]
fn test_brotli_decompress_empty() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let decompressed = compressor.decompress(b"").unwrap();
assert_eq!(decompressed.len(), 0);
}
#[test]
fn test_brotli_decompress_uncompressed_data() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let data = b"This is not brotli compressed";
let result = compressor.decompress(data).unwrap();
assert_eq!(result, data);
}
#[test]
fn test_brotli_compression_levels() {
let data = b"This is test data that should compress. ".repeat(100);
let fast = BrotliCompressor::fast().compress(&data).unwrap();
let default = BrotliCompressor::default_level().compress(&data).unwrap();
let best = BrotliCompressor::best().compress(&data).unwrap();
assert!(best.len() <= default.len() || best.len() < 100);
assert!(default.len() <= fast.len() || default.len() < 100);
let decompressor = BrotliCompressor::default_level();
let fast_dec = decompressor.decompress(&fast).unwrap();
let default_dec = decompressor.decompress(&default).unwrap();
let best_dec = decompressor.decompress(&best).unwrap();
assert_eq!(fast_dec, data);
assert_eq!(default_dec, data);
assert_eq!(best_dec, data);
}
#[test]
fn test_brotli_large_data() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let original = vec![0x42u8; 1024 * 100];
let compressed = compressor.compress(&original).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(original, decompressed);
assert!(compressed.len() < original.len() / 50);
}
#[test]
fn test_brotli_debug_format() {
let compressor = BrotliCompressor::new(CompressionLevel::Default);
let debug_str = format!("{:?}", compressor);
assert!(debug_str.contains("BrotliCompressor"));
assert!(debug_str.contains("Default"));
}
}