2025-03-19 00:44:27 +01:00
|
|
|
use async_trait::async_trait;
|
|
|
|
|
use bytes::Bytes;
|
|
|
|
|
use flate2::Compression;
|
|
|
|
|
use flate2::bufread::GzDecoder;
|
2026-02-14 01:29:34 +01:00
|
|
|
use flate2::read::GzEncoder as GzEncoderRead;
|
|
|
|
|
use futures::{Stream, StreamExt};
|
|
|
|
|
use std::io;
|
|
|
|
|
use std::io::Read;
|
|
|
|
|
use tracing::error;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
use crate::application::ports::compression_ports::{
|
2026-02-14 01:29:34 +01:00
|
|
|
CompressionLevel as PortCompressionLevel, CompressionPort,
|
2026-02-08 13:40:23 +01:00
|
|
|
};
|
|
|
|
|
use crate::domain::errors::DomainError;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Compression level for files
|
2025-03-19 00:44:27 +01:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub enum CompressionLevel {
|
2026-02-12 09:41:25 +01:00
|
|
|
/// No compression (transfer only)
|
2025-03-19 00:44:27 +01:00
|
|
|
None = 0,
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Fast compression with lower ratio
|
2025-03-19 00:44:27 +01:00
|
|
|
Fast = 1,
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Balanced compression (default)
|
2025-03-19 00:44:27 +01:00
|
|
|
Default = 6,
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Maximum compression (slower)
|
2025-03-19 00:44:27 +01:00
|
|
|
Best = 9,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<CompressionLevel> for Compression {
|
|
|
|
|
fn from(level: CompressionLevel) -> Self {
|
|
|
|
|
match level {
|
|
|
|
|
CompressionLevel::None => Compression::none(),
|
|
|
|
|
CompressionLevel::Fast => Compression::fast(),
|
|
|
|
|
CompressionLevel::Default => Compression::default(),
|
|
|
|
|
CompressionLevel::Best => Compression::best(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Size threshold to decide whether to compress or not
|
2025-03-19 00:44:27 +01:00
|
|
|
const COMPRESSION_SIZE_THRESHOLD: u64 = 1024 * 50; // 50KB
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Interface for compression services
|
2025-03-19 00:44:27 +01:00
|
|
|
#[async_trait]
|
|
|
|
|
pub trait CompressionService: Send + Sync {
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Compresses data in memory
|
2025-03-19 00:44:27 +01:00
|
|
|
async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>>;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Decompresses data in memory
|
2025-03-19 00:44:27 +01:00
|
|
|
async fn decompress_data(&self, compressed_data: &[u8]) -> io::Result<Vec<u8>>;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Compresses a data stream
|
2026-02-14 01:29:34 +01:00
|
|
|
fn compress_stream<S>(
|
|
|
|
|
&self,
|
|
|
|
|
stream: S,
|
|
|
|
|
level: CompressionLevel,
|
|
|
|
|
) -> impl Stream<Item = io::Result<Bytes>> + Send
|
2025-03-19 00:44:27 +01:00
|
|
|
where
|
|
|
|
|
S: Stream<Item = io::Result<Bytes>> + Send + 'static + Unpin;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Decompresses a data stream
|
2026-02-14 01:29:34 +01:00
|
|
|
fn decompress_stream<S>(
|
|
|
|
|
&self,
|
|
|
|
|
compressed_stream: S,
|
|
|
|
|
) -> impl Stream<Item = io::Result<Bytes>> + Send
|
2025-03-19 00:44:27 +01:00
|
|
|
where
|
|
|
|
|
S: Stream<Item = io::Result<Bytes>> + Send + 'static + Unpin;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Determines whether a file should be compressed based on its MIME type and size
|
2025-03-19 00:44:27 +01:00
|
|
|
fn should_compress(&self, mime_type: &str, size: u64) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Gzip compression service implementation
|
2026-02-14 17:54:25 +01:00
|
|
|
pub struct GzipCompressionService;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
|
|
|
|
impl GzipCompressionService {
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Creates a new service instance
|
2025-03-19 00:44:27 +01:00
|
|
|
pub fn new() -> Self {
|
2026-02-14 17:54:25 +01:00
|
|
|
Self
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl CompressionService for GzipCompressionService {
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Compresses data in memory using Gzip
|
2025-03-19 00:44:27 +01:00
|
|
|
async fn compress_data(&self, data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>> {
|
|
|
|
|
let data_owned = data.to_vec();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
|
let mut encoder = GzEncoderRead::new(&data_owned[..], level.into());
|
|
|
|
|
let mut compressed = Vec::new();
|
|
|
|
|
encoder.read_to_end(&mut compressed)?;
|
|
|
|
|
Ok(compressed)
|
2026-02-14 01:29:34 +01:00
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_else(|e| {
|
2026-02-12 09:41:25 +01:00
|
|
|
error!("Compression task error: {}", e);
|
2026-02-14 01:26:02 +01:00
|
|
|
Err(io::Error::other(e.to_string()))
|
2025-03-19 00:44:27 +01:00
|
|
|
})
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Decompresses data in memory
|
2025-03-19 00:44:27 +01:00
|
|
|
async fn decompress_data(&self, compressed_data: &[u8]) -> io::Result<Vec<u8>> {
|
2026-02-14 17:54:25 +01:00
|
|
|
let data = compressed_data.to_vec();
|
2025-03-19 00:44:27 +01:00
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
|
let mut decoder = GzDecoder::new(&data[..]);
|
|
|
|
|
let mut decompressed = Vec::new();
|
|
|
|
|
decoder.read_to_end(&mut decompressed)?;
|
|
|
|
|
Ok(decompressed)
|
2026-02-14 01:29:34 +01:00
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_else(|e| {
|
2026-02-12 09:41:25 +01:00
|
|
|
error!("Decompression task error: {}", e);
|
2026-02-14 01:26:02 +01:00
|
|
|
Err(io::Error::other(e.to_string()))
|
2025-03-19 00:44:27 +01:00
|
|
|
})
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Compresses a byte stream
|
2026-02-14 01:29:34 +01:00
|
|
|
fn compress_stream<S>(
|
|
|
|
|
&self,
|
|
|
|
|
stream: S,
|
|
|
|
|
level: CompressionLevel,
|
|
|
|
|
) -> impl Stream<Item = io::Result<Bytes>> + Send
|
2025-03-19 00:44:27 +01:00
|
|
|
where
|
2026-02-14 01:29:34 +01:00
|
|
|
S: Stream<Item = io::Result<Bytes>> + Send + 'static + Unpin,
|
2025-03-19 00:44:27 +01:00
|
|
|
{
|
|
|
|
|
// For now, simplify the implementation to avoid complex pinning issues
|
|
|
|
|
// This implementation collects all stream data and then compresses it at once
|
|
|
|
|
// Future optimization would be to implement true streaming compression
|
|
|
|
|
let compression_level = level;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
Box::pin(async_stream::stream! {
|
|
|
|
|
let mut data = Vec::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Collect all bytes from the stream
|
|
|
|
|
let mut stream = Box::pin(stream);
|
|
|
|
|
while let Some(result) = stream.next().await {
|
|
|
|
|
match result {
|
|
|
|
|
Ok(bytes) => {
|
|
|
|
|
data.extend_from_slice(&bytes);
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
yield Err(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Compress collected data
|
2026-02-08 13:40:23 +01:00
|
|
|
match CompressionService::compress_data(self, &data, compression_level).await {
|
2025-03-19 00:44:27 +01:00
|
|
|
Ok(compressed) => {
|
|
|
|
|
// Return compressed data as a single chunk
|
|
|
|
|
yield Ok(Bytes::from(compressed));
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
yield Err(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Decompresses a byte stream
|
2026-02-14 01:29:34 +01:00
|
|
|
fn decompress_stream<S>(
|
|
|
|
|
&self,
|
|
|
|
|
compressed_stream: S,
|
|
|
|
|
) -> impl Stream<Item = io::Result<Bytes>> + Send
|
2025-03-19 00:44:27 +01:00
|
|
|
where
|
2026-02-14 01:29:34 +01:00
|
|
|
S: Stream<Item = io::Result<Bytes>> + Send + 'static + Unpin,
|
2025-03-19 00:44:27 +01:00
|
|
|
{
|
|
|
|
|
// For now, simplify the implementation to avoid complex pinning issues
|
|
|
|
|
// This implementation collects all stream data and then decompresses it at once
|
|
|
|
|
// Future optimization would be to implement streaming decompression correctly
|
|
|
|
|
Box::pin(async_stream::stream! {
|
|
|
|
|
let mut compressed_data = Vec::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Collect all bytes from the stream
|
|
|
|
|
let mut stream = Box::pin(compressed_stream);
|
|
|
|
|
while let Some(result) = stream.next().await {
|
|
|
|
|
match result {
|
|
|
|
|
Ok(bytes) => {
|
|
|
|
|
compressed_data.extend_from_slice(&bytes);
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
yield Err(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Decompress collected data
|
2026-02-08 13:40:23 +01:00
|
|
|
match CompressionService::decompress_data(self, &compressed_data).await {
|
2025-03-19 00:44:27 +01:00
|
|
|
Ok(decompressed) => {
|
|
|
|
|
// Return decompressed data as a single chunk
|
|
|
|
|
yield Ok(Bytes::from(decompressed));
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
yield Err(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
/// Determines whether a file should be compressed based on its MIME type and size
|
2025-03-19 00:44:27 +01:00
|
|
|
fn should_compress(&self, mime_type: &str, size: u64) -> bool {
|
2026-02-12 09:41:25 +01:00
|
|
|
// Do not compress very small files (overhead)
|
2025-03-19 00:44:27 +01:00
|
|
|
if size < COMPRESSION_SIZE_THRESHOLD {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Do not compress already compressed files
|
2025-03-19 00:44:27 +01:00
|
|
|
if mime_type.starts_with("image/")
|
|
|
|
|
&& !mime_type.contains("svg")
|
2026-02-14 01:29:34 +01:00
|
|
|
&& !mime_type.contains("bmp")
|
|
|
|
|
{
|
2025-03-19 00:44:27 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
|
|
|
|
if mime_type.starts_with("audio/")
|
|
|
|
|
|| mime_type.starts_with("video/")
|
2025-03-19 00:44:27 +01:00
|
|
|
|| mime_type.contains("zip")
|
|
|
|
|
|| mime_type.contains("gzip")
|
|
|
|
|
|| mime_type.contains("compressed")
|
|
|
|
|
|| mime_type.contains("7z")
|
|
|
|
|
|| mime_type.contains("rar")
|
|
|
|
|
|| mime_type.contains("bz2")
|
|
|
|
|
|| mime_type.contains("xz")
|
|
|
|
|
|| mime_type.contains("jpg")
|
|
|
|
|
|| mime_type.contains("jpeg")
|
|
|
|
|
|| mime_type.contains("png")
|
|
|
|
|
|| mime_type.contains("gif")
|
|
|
|
|
|| mime_type.contains("webp")
|
|
|
|
|
|| mime_type.contains("mp3")
|
|
|
|
|
|| mime_type.contains("mp4")
|
|
|
|
|
|| mime_type.contains("ogg")
|
2026-02-14 01:29:34 +01:00
|
|
|
|| mime_type.contains("webm")
|
|
|
|
|
{
|
2025-03-19 00:44:27 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Compress text files, documents, and other compressible types
|
2025-03-19 00:44:27 +01:00
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// ─── Port implementation ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// Convert application-layer CompressionLevel to infrastructure CompressionLevel.
|
|
|
|
|
impl From<PortCompressionLevel> for CompressionLevel {
|
|
|
|
|
fn from(level: PortCompressionLevel) -> Self {
|
|
|
|
|
match level {
|
|
|
|
|
PortCompressionLevel::None => CompressionLevel::None,
|
|
|
|
|
PortCompressionLevel::Fast => CompressionLevel::Fast,
|
|
|
|
|
PortCompressionLevel::Default => CompressionLevel::Default,
|
|
|
|
|
PortCompressionLevel::Best => CompressionLevel::Best,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl CompressionPort for GzipCompressionService {
|
2026-02-14 01:29:34 +01:00
|
|
|
async fn compress_data(
|
|
|
|
|
&self,
|
|
|
|
|
data: &[u8],
|
|
|
|
|
level: PortCompressionLevel,
|
|
|
|
|
) -> Result<Vec<u8>, DomainError> {
|
|
|
|
|
CompressionService::compress_data(self, data, level.into())
|
|
|
|
|
.await
|
|
|
|
|
.map_err(DomainError::from)
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn decompress_data(&self, compressed_data: &[u8]) -> Result<Vec<u8>, DomainError> {
|
2026-02-14 01:29:34 +01:00
|
|
|
CompressionService::decompress_data(self, compressed_data)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(DomainError::from)
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn should_compress(&self, mime_type: &str, size: u64) -> bool {
|
|
|
|
|
CompressionService::should_compress(self, mime_type, size)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use futures::TryStreamExt;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_compress_decompress_data() {
|
|
|
|
|
let service = GzipCompressionService::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Test data
|
2025-03-19 00:44:27 +01:00
|
|
|
let data = "Hello, world! ".repeat(1000).into_bytes();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Compress
|
2026-02-14 01:29:34 +01:00
|
|
|
let compressed =
|
|
|
|
|
CompressionService::compress_data(&service, &data, CompressionLevel::Default)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Verify that compression reduces the size
|
2025-03-19 00:44:27 +01:00
|
|
|
assert!(compressed.len() < data.len());
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Decompress
|
2026-02-14 01:29:34 +01:00
|
|
|
let decompressed = CompressionService::decompress_data(&service, &compressed)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Verify that the original data is recovered correctly
|
2025-03-19 00:44:27 +01:00
|
|
|
assert_eq!(decompressed, data);
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn test_compress_decompress_stream() {
|
|
|
|
|
let service = GzipCompressionService::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Create test data
|
2025-03-19 00:44:27 +01:00
|
|
|
let chunks = vec![
|
|
|
|
|
Ok(Bytes::from("Hello, ")),
|
|
|
|
|
Ok(Bytes::from("world! ")),
|
|
|
|
|
Ok(Bytes::from("This is a test of streaming compression.")),
|
|
|
|
|
];
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Convert to stream
|
2025-03-19 00:44:27 +01:00
|
|
|
let input_stream = futures::stream::iter(chunks);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Compress the stream
|
2025-03-19 00:44:27 +01:00
|
|
|
let compressed_stream = service.compress_stream(input_stream, CompressionLevel::Default);
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Collect the compressed bytes
|
2025-03-19 00:44:27 +01:00
|
|
|
let compressed_bytes = compressed_stream
|
|
|
|
|
.try_fold(Vec::new(), |mut acc, chunk| async move {
|
|
|
|
|
acc.extend_from_slice(&chunk);
|
|
|
|
|
Ok(acc)
|
2026-02-14 01:29:34 +01:00
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Decompress the data
|
2026-02-14 01:29:34 +01:00
|
|
|
let decompressed = CompressionService::decompress_data(&service, &compressed_bytes)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Verify result
|
2025-03-19 00:44:27 +01:00
|
|
|
let expected = "Hello, world! This is a test of streaming compression.";
|
|
|
|
|
assert_eq!(String::from_utf8(decompressed).unwrap(), expected);
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
#[test]
|
|
|
|
|
fn test_should_compress() {
|
|
|
|
|
let service = GzipCompressionService::new();
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Cases that should not be compressed
|
2026-02-14 01:29:34 +01:00
|
|
|
assert!(!CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
100 * 1024
|
|
|
|
|
));
|
|
|
|
|
assert!(!CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"video/mp4",
|
|
|
|
|
10 * 1024 * 1024
|
|
|
|
|
));
|
|
|
|
|
assert!(!CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"application/zip",
|
|
|
|
|
5 * 1024 * 1024
|
|
|
|
|
));
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Cases that should be compressed
|
2026-02-14 01:29:34 +01:00
|
|
|
assert!(CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"text/html",
|
|
|
|
|
100 * 1024
|
|
|
|
|
));
|
|
|
|
|
assert!(CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"application/json",
|
|
|
|
|
200 * 1024
|
|
|
|
|
));
|
|
|
|
|
assert!(CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"text/plain",
|
|
|
|
|
1024 * 1024
|
|
|
|
|
));
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Small files should not be compressed regardless of type
|
2026-02-14 01:29:34 +01:00
|
|
|
assert!(!CompressionService::should_compress(
|
|
|
|
|
&service,
|
|
|
|
|
"text/html",
|
|
|
|
|
10 * 1024
|
|
|
|
|
));
|
2025-03-19 00:44:27 +01:00
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|