use std::io::{ Read, Write };
use flate2::{ Compression as FlateLevel, read::{ GzDecoder, DeflateDecoder }, write::{ GzEncoder, DeflateEncoder } };
use crate::error::Error;
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum CompressionAlgorithm
{
Gzip,
Deflate,
Brotli,
None,
}
impl CompressionAlgorithm
{
#[ must_use ]
pub fn content_encoding( &self ) -> Option< &'static str >
{
match self
{
Self::Gzip => Some( "gzip" ),
Self::Deflate => Some( "deflate" ),
Self::Brotli => Some( "br" ),
Self::None => None,
}
}
}
impl Default for CompressionAlgorithm
{
#[ inline ]
fn default() -> Self
{
Self::Gzip
}
}
#[ derive( Debug, Clone ) ]
pub struct CompressionConfig
{
pub algorithm : CompressionAlgorithm,
pub level : u32,
pub min_size : usize,
}
impl CompressionConfig
{
#[ must_use ]
pub fn new() -> Self
{
Self {
algorithm : CompressionAlgorithm::Gzip,
level : 6,
min_size : 1024,
}
}
#[ must_use ]
pub fn algorithm( mut self, algorithm : CompressionAlgorithm ) -> Self
{
self.algorithm = algorithm;
self
}
#[ must_use ]
pub fn level( mut self, level : u32 ) -> Self
{
self.level = level;
self
}
#[ must_use ]
pub fn min_size( mut self, min_size : usize ) -> Self
{
self.min_size = min_size;
self
}
}
impl Default for CompressionConfig
{
#[ inline ]
fn default() -> Self
{
Self::new()
}
}
pub fn compress( data : &[ u8 ], config : &CompressionConfig ) -> Result< Vec< u8 >, Error >
{
if data.len() < config.min_size
{
return Ok( data.to_vec() );
}
match config.algorithm
{
CompressionAlgorithm::Gzip => {
let mut encoder = GzEncoder::new( Vec::new(), FlateLevel::new( config.level ) );
encoder.write_all( data )
.map_err( | e | Error::ConfigurationError( format!( "Gzip compression failed : {}", e ) ) )?;
encoder.finish()
.map_err( | e | Error::ConfigurationError( format!( "Gzip compression finish failed : {}", e ) ) )
},
CompressionAlgorithm::Deflate => {
let mut encoder = DeflateEncoder::new( Vec::new(), FlateLevel::new( config.level ) );
encoder.write_all( data )
.map_err( | e | Error::ConfigurationError( format!( "Deflate compression failed : {}", e ) ) )?;
encoder.finish()
.map_err( | e | Error::ConfigurationError( format!( "Deflate compression finish failed : {}", e ) ) )
},
CompressionAlgorithm::Brotli => {
use brotli::enc::BrotliEncoderParams;
let params = BrotliEncoderParams {
quality : config.level as i32,
..Default::default()
};
let mut output = Vec::new();
{
let mut compressor = brotli::CompressorWriter::with_params(
&mut output,
4096,
¶ms
);
compressor.write_all( data )
.map_err( | e | Error::ConfigurationError( format!( "Brotli compression failed : {}", e ) ) )?;
}
Ok( output )
},
CompressionAlgorithm::None => Ok( data.to_vec() ),
}
}
pub fn decompress( data : &[ u8 ], algorithm : CompressionAlgorithm ) -> Result< Vec< u8 >, Error >
{
match algorithm
{
CompressionAlgorithm::Gzip => {
let mut decoder = GzDecoder::new( data );
let mut output = Vec::new();
decoder.read_to_end( &mut output )
.map_err( | e | Error::ConfigurationError( format!( "Gzip decompression failed : {}", e ) ) )?;
Ok( output )
},
CompressionAlgorithm::Deflate => {
let mut decoder = DeflateDecoder::new( data );
let mut output = Vec::new();
decoder.read_to_end( &mut output )
.map_err( | e | Error::ConfigurationError( format!( "Deflate decompression failed : {}", e ) ) )?;
Ok( output )
},
CompressionAlgorithm::Brotli => {
let mut output = Vec::new();
{
let mut decompressor = brotli::Decompressor::new( data, 4096 );
decompressor.read_to_end( &mut output )
.map_err( | e | Error::ConfigurationError( format!( "Brotli decompression failed : {}", e ) ) )?;
}
Ok( output )
},
CompressionAlgorithm::None => Ok( data.to_vec() ),
}
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn test_compression_config_defaults()
{
let config = CompressionConfig::new();
assert_eq!( config.algorithm, CompressionAlgorithm::Gzip );
assert_eq!( config.level, 6 );
assert_eq!( config.min_size, 1024 );
}
#[ test ]
fn test_compression_config_builder()
{
let config = CompressionConfig::new()
.algorithm( CompressionAlgorithm::Brotli )
.level( 9 )
.min_size( 2048 );
assert_eq!( config.algorithm, CompressionAlgorithm::Brotli );
assert_eq!( config.level, 9 );
assert_eq!( config.min_size, 2048 );
}
#[ test ]
fn test_gzip_compression_roundtrip()
{
let data = "Hello, World! This is a test of gzip compression. ".repeat( 50 );
let data_bytes = data.as_bytes();
let config = CompressionConfig::new()
.algorithm( CompressionAlgorithm::Gzip )
.min_size( 0 );
let compressed = compress( data_bytes, &config ).unwrap();
assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );
let decompressed = decompress( &compressed, CompressionAlgorithm::Gzip ).unwrap();
assert_eq!( &decompressed[ .. ], data_bytes );
}
#[ test ]
fn test_deflate_compression_roundtrip()
{
let data = "Hello, World! This is a test of deflate compression. ".repeat( 50 );
let data_bytes = data.as_bytes();
let config = CompressionConfig::new()
.algorithm( CompressionAlgorithm::Deflate )
.min_size( 0 );
let compressed = compress( data_bytes, &config ).unwrap();
assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );
let decompressed = decompress( &compressed, CompressionAlgorithm::Deflate ).unwrap();
assert_eq!( &decompressed[ .. ], data_bytes );
}
#[ test ]
fn test_brotli_compression_roundtrip()
{
let data = "Hello, World! This is a test of brotli compression. ".repeat( 50 );
let data_bytes = data.as_bytes();
let config = CompressionConfig::new()
.algorithm( CompressionAlgorithm::Brotli )
.level( 6 )
.min_size( 0 );
let compressed = compress( data_bytes, &config ).unwrap();
assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );
let decompressed = decompress( &compressed, CompressionAlgorithm::Brotli ).unwrap();
assert_eq!( &decompressed[ .. ], data_bytes );
}
#[ test ]
fn test_min_size_threshold()
{
let small_data = b"Hi";
let config = CompressionConfig::new().min_size( 1024 );
let result = compress( small_data, &config ).unwrap();
assert_eq!( &result[ .. ], small_data, "Small data should not be compressed" );
}
#[ test ]
fn test_content_encoding_header()
{
assert_eq!( CompressionAlgorithm::Gzip.content_encoding(), Some( "gzip" ) );
assert_eq!( CompressionAlgorithm::Deflate.content_encoding(), Some( "deflate" ) );
assert_eq!( CompressionAlgorithm::Brotli.content_encoding(), Some( "br" ) );
assert_eq!( CompressionAlgorithm::None.content_encoding(), None );
}
}