skp_cache_core/
compression.rs1use crate::CacheError;
6
7pub const DEFAULT_COMPRESSION_LEVEL: i32 = 3;
9
10pub const MIN_COMPRESSION_SIZE: usize = 256;
13
14pub trait Compressor: Send + Sync + Clone + 'static {
16 fn name(&self) -> &str;
18
19 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError>;
21
22 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError>;
24
25 fn should_compress(&self, data: &[u8]) -> bool {
27 data.len() >= MIN_COMPRESSION_SIZE
28 }
29}
30
31#[derive(Debug, Clone, Copy, Default)]
33pub struct NoopCompressor;
34
35impl Compressor for NoopCompressor {
36 fn name(&self) -> &str {
37 "none"
38 }
39
40 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError> {
41 Ok(data.to_vec())
42 }
43
44 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError> {
45 Ok(data.to_vec())
46 }
47
48 fn should_compress(&self, _data: &[u8]) -> bool {
49 false
50 }
51}
52
53#[cfg(feature = "compression")]
55#[derive(Debug, Clone)]
56pub struct ZstdCompressor {
57 level: i32,
58 min_size: usize,
59}
60
61#[cfg(feature = "compression")]
62impl Default for ZstdCompressor {
63 fn default() -> Self {
64 Self::new(DEFAULT_COMPRESSION_LEVEL)
65 }
66}
67
68#[cfg(feature = "compression")]
69impl ZstdCompressor {
70 pub fn new(level: i32) -> Self {
72 Self {
73 level: level.clamp(1, 22),
74 min_size: MIN_COMPRESSION_SIZE,
75 }
76 }
77
78 pub fn with_min_size(mut self, size: usize) -> Self {
80 self.min_size = size;
81 self
82 }
83
84 pub fn level(&self) -> i32 {
86 self.level
87 }
88}
89
90#[cfg(feature = "compression")]
91impl Compressor for ZstdCompressor {
92 fn name(&self) -> &str {
93 "zstd"
94 }
95
96 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError> {
97 zstd::encode_all(data, self.level)
98 .map_err(|e| CacheError::Compression(e.to_string()))
99 }
100
101 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CacheError> {
102 zstd::decode_all(data)
103 .map_err(|e| CacheError::Decompression(e.to_string()))
104 }
105
106 fn should_compress(&self, data: &[u8]) -> bool {
107 data.len() >= self.min_size
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_noop_compressor() {
117 let compressor = NoopCompressor;
118 let data = b"hello world";
119
120 let compressed = compressor.compress(data).unwrap();
121 assert_eq!(compressed, data);
122
123 let decompressed = compressor.decompress(&compressed).unwrap();
124 assert_eq!(decompressed, data);
125
126 assert!(!compressor.should_compress(data));
127 }
128
129 #[cfg(feature = "compression")]
130 #[test]
131 fn test_zstd_compressor() {
132 let compressor = ZstdCompressor::new(3);
133
134 let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
136
137 let compressed = compressor.compress(&data).unwrap();
138 assert!(compressed.len() < data.len());
140
141 let decompressed = compressor.decompress(&compressed).unwrap();
142 assert_eq!(decompressed, data);
143 }
144
145 #[cfg(feature = "compression")]
146 #[test]
147 fn test_zstd_should_compress() {
148 let compressor = ZstdCompressor::new(3);
149
150 assert!(!compressor.should_compress(b"small"));
152
153 let large: Vec<u8> = vec![0; 1024];
155 assert!(compressor.should_compress(&large));
156 }
157
158 #[cfg(feature = "compression")]
159 #[test]
160 fn test_zstd_level_clamping() {
161 let low = ZstdCompressor::new(-5);
162 assert_eq!(low.level(), 1);
163
164 let high = ZstdCompressor::new(100);
165 assert_eq!(high.level(), 22);
166 }
167}