use bytes::Bytes;
use flate2::read::GzDecoder;
use std::io::Read;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionEncoding {
Gzip,
Deflate,
Brotli,
Identity, }
impl CompressionEncoding {
pub fn from_header(header: &str) -> Option<Self> {
match header.to_lowercase().trim() {
"gzip" => Some(CompressionEncoding::Gzip),
"deflate" => Some(CompressionEncoding::Deflate),
"br" => Some(CompressionEncoding::Brotli),
"identity" | "" | "none" => Some(CompressionEncoding::Identity),
_ => None,
}
}
pub fn as_str(&self) -> &str {
match self {
CompressionEncoding::Gzip => "gzip",
CompressionEncoding::Deflate => "deflate",
CompressionEncoding::Brotli => "br",
CompressionEncoding::Identity => "identity",
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum DecompressionError {
#[error("Unsupported compression encoding: {0}")]
Unsupported(String),
#[error("Decompression failed: {0}")]
Failed(String),
}
pub struct Decompressor;
impl Decompressor {
pub fn decompress(data: Bytes, encoding: CompressionEncoding) -> Result<Bytes, DecompressionError> {
match encoding {
CompressionEncoding::Gzip => {
Self::decompress_gzip(data)
}
CompressionEncoding::Deflate => {
Self::decompress_deflate(data)
}
CompressionEncoding::Brotli => {
Self::decompress_brotli(data)
}
CompressionEncoding::Identity => {
Ok(data) }
}
}
fn decompress_gzip(data: Bytes) -> Result<Bytes, DecompressionError> {
let mut decoder = GzDecoder::new(&*data);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| DecompressionError::Failed(format!("Gzip: {}", e)))?;
Ok(Bytes::from(decompressed))
}
fn decompress_deflate(data: Bytes) -> Result<Bytes, DecompressionError> {
use flate2::read::ZlibDecoder;
let mut decoder = ZlibDecoder::new(&*data);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| DecompressionError::Failed(format!("Deflate: {}", e)))?;
Ok(Bytes::from(decompressed))
}
fn decompress_brotli(data: Bytes) -> Result<Bytes, DecompressionError> {
let mut decompressed = Vec::new();
let mut reader = brotli::Decompressor::new(&*data, 4096);
reader
.read_to_end(&mut decompressed)
.map_err(|e| DecompressionError::Failed(format!("Brotli: {}", e)))?;
Ok(Bytes::from(decompressed))
}
pub fn auto_decompress(data: Bytes, content_encoding: Option<&str>) -> Result<Bytes, DecompressionError> {
match content_encoding {
Some(encoding) => {
match CompressionEncoding::from_header(encoding) {
Some(enc) => Self::decompress(data, enc),
None => Ok(data), }
}
None => Ok(data), }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compression_encoding_from_header() {
assert_eq!(
CompressionEncoding::from_header("gzip"),
Some(CompressionEncoding::Gzip)
);
assert_eq!(
CompressionEncoding::from_header("GZIP"),
Some(CompressionEncoding::Gzip)
);
assert_eq!(
CompressionEncoding::from_header("deflate"),
Some(CompressionEncoding::Deflate)
);
assert_eq!(
CompressionEncoding::from_header("br"),
Some(CompressionEncoding::Brotli)
);
assert_eq!(
CompressionEncoding::from_header("identity"),
Some(CompressionEncoding::Identity)
);
assert_eq!(
CompressionEncoding::from_header("unknown"),
None
);
}
#[test]
fn test_compression_encoding_as_str() {
assert_eq!(CompressionEncoding::Gzip.as_str(), "gzip");
assert_eq!(CompressionEncoding::Deflate.as_str(), "deflate");
assert_eq!(CompressionEncoding::Brotli.as_str(), "br");
assert_eq!(CompressionEncoding::Identity.as_str(), "identity");
}
#[test]
fn test_decompress_identity() {
let data = Bytes::from_static(b"hello world");
let result = Decompressor::decompress(data, CompressionEncoding::Identity);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Bytes::from_static(b"hello world"));
}
#[test]
fn test_decompress_gzip() {
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
let original = Bytes::from_static(b"hello world");
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = Bytes::from(encoder.finish().unwrap());
let result = Decompressor::decompress(compressed, CompressionEncoding::Gzip);
assert!(result.is_ok());
assert_eq!(result.unwrap(), original);
}
#[test]
fn test_auto_decompress() {
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
let original = Bytes::from_static(b"test data");
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = Bytes::from(encoder.finish().unwrap());
let result = Decompressor::auto_decompress(compressed.clone(), Some("gzip"));
assert!(result.is_ok());
assert_eq!(result.unwrap(), original);
let result = Decompressor::auto_decompress(compressed.clone(), None);
assert!(result.is_ok());
assert_eq!(result.unwrap(), compressed);
}
#[test]
fn test_decompress_deflate() {
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::io::Write;
let original = Bytes::from_static(b"deflate test");
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = Bytes::from(encoder.finish().unwrap());
let result = Decompressor::decompress(compressed, CompressionEncoding::Deflate);
assert!(result.is_ok());
assert_eq!(result.unwrap(), original);
}
#[test]
fn test_decompress_brotli() {
let _original = Bytes::from_static(b"brotli test");
}
#[test]
fn test_decompress_empty() {
let empty = Bytes::new();
let identity_result = Decompressor::decompress(empty.clone(), CompressionEncoding::Identity);
assert!(identity_result.is_ok());
assert_eq!(identity_result.unwrap(), empty);
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(b"").unwrap();
let empty_compressed = Bytes::from(encoder.finish().unwrap());
let gzip_result = Decompressor::decompress(empty_compressed, CompressionEncoding::Gzip);
assert!(gzip_result.is_ok());
}
#[test]
fn test_decompress_invalid_gzip() {
let invalid = Bytes::from_static(b"not valid gzip data");
let result = Decompressor::decompress(invalid, CompressionEncoding::Gzip);
assert!(result.is_err());
}
#[test]
fn test_decompress_invalid_deflate() {
let invalid = Bytes::from_static(b"not valid deflate data");
let result = Decompressor::decompress(invalid, CompressionEncoding::Deflate);
assert!(result.is_err());
}
#[test]
fn test_decompress_invalid_brotli() {
let invalid = Bytes::from_static(b"not valid brotli data");
let result = Decompressor::decompress(invalid, CompressionEncoding::Brotli);
assert!(result.is_err());
}
#[test]
fn test_auto_decompress_unsupported_encoding() {
let data = Bytes::from_static(b"test data");
let result = Decompressor::auto_decompress(data.clone(), Some("unsupported"));
assert!(result.is_ok());
assert_eq!(result.unwrap(), data);
}
#[test]
fn test_compression_encoding_case_insensitive() {
assert_eq!(
CompressionEncoding::from_header("GZIP"),
Some(CompressionEncoding::Gzip)
);
assert_eq!(
CompressionEncoding::from_header("Deflate"),
Some(CompressionEncoding::Deflate)
);
assert_eq!(
CompressionEncoding::from_header("BR"),
Some(CompressionEncoding::Brotli)
);
assert_eq!(
CompressionEncoding::from_header("Identity"),
Some(CompressionEncoding::Identity)
);
}
#[test]
fn test_compression_encoding_whitespace() {
assert_eq!(
CompressionEncoding::from_header(" gzip "),
Some(CompressionEncoding::Gzip)
);
assert_eq!(
CompressionEncoding::from_header("\tdeflate\t"),
Some(CompressionEncoding::Deflate)
);
}
#[test]
fn test_compression_encoding_empty() {
assert_eq!(
CompressionEncoding::from_header(""),
Some(CompressionEncoding::Identity)
);
assert_eq!(
CompressionEncoding::from_header("none"),
Some(CompressionEncoding::Identity)
);
}
#[test]
fn test_compression_encoding_equality() {
assert_eq!(CompressionEncoding::Gzip, CompressionEncoding::Gzip);
assert_eq!(CompressionEncoding::Deflate, CompressionEncoding::Deflate);
assert_eq!(CompressionEncoding::Brotli, CompressionEncoding::Brotli);
assert_eq!(CompressionEncoding::Identity, CompressionEncoding::Identity);
assert_ne!(CompressionEncoding::Gzip, CompressionEncoding::Deflate);
}
#[test]
fn test_decompression_error_debug() {
let error = DecompressionError::Unsupported("unknown".to_string());
let debug_str = format!("{:?}", error);
assert!(debug_str.contains("Unsupported"));
let error = DecompressionError::Failed("failed".to_string());
let debug_str = format!("{:?}", error);
assert!(debug_str.contains("Failed"));
}
#[test]
fn test_decompression_error_display() {
let error = DecompressionError::Unsupported("test".to_string());
assert!(error.to_string().contains("Unsupported"));
let error = DecompressionError::Failed("test".to_string());
assert!(error.to_string().contains("Decompression failed"));
}
#[test]
fn test_large_data_decompression() {
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
let mut large_data = Vec::new();
for i in 0..1000 {
large_data.extend_from_slice(format!("line {}\n", i).as_bytes());
}
let original = Bytes::from(large_data);
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = Bytes::from(encoder.finish().unwrap());
let result = Decompressor::decompress(compressed, CompressionEncoding::Gzip);
assert!(result.is_ok());
assert_eq!(result.unwrap(), original);
}
#[test]
fn test_binary_data_decompression() {
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
let mut binary_data = Vec::new();
for i in 0u8..=255 {
binary_data.push(i);
}
let original = Bytes::from(binary_data);
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = Bytes::from(encoder.finish().unwrap());
let result = Decompressor::decompress(compressed, CompressionEncoding::Gzip);
assert!(result.is_ok());
assert_eq!(result.unwrap(), original);
}
}