Skip to main content

cyanea_core/
compress.rs

1//! Compression utilities with algorithm auto-detection.
2
3use std::io::{Read, Write};
4
5use crate::{CyaneaError, Result};
6
7/// Supported compression algorithms.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Algorithm {
10    Zstd,
11    Gzip,
12}
13
14/// Compress data using zstd at the given level (1–22).
15pub fn zstd_compress(data: &[u8], level: i32) -> Result<Vec<u8>> {
16    zstd::encode_all(data, level).map_err(|e| CyaneaError::Compression(e.to_string()))
17}
18
19/// Decompress zstd data.
20pub fn zstd_decompress(data: &[u8]) -> Result<Vec<u8>> {
21    zstd::decode_all(data).map_err(|e| CyaneaError::Compression(e.to_string()))
22}
23
24/// Compress data using gzip at the given level (0–9).
25pub fn gzip_compress(data: &[u8], level: u32) -> Result<Vec<u8>> {
26    use flate2::write::GzEncoder;
27    use flate2::Compression;
28
29    let mut encoder = GzEncoder::new(Vec::new(), Compression::new(level));
30    encoder
31        .write_all(data)
32        .map_err(|e| CyaneaError::Compression(e.to_string()))?;
33    encoder
34        .finish()
35        .map_err(|e| CyaneaError::Compression(e.to_string()))
36}
37
38/// Decompress gzip data.
39pub fn gzip_decompress(data: &[u8]) -> Result<Vec<u8>> {
40    use flate2::read::GzDecoder;
41
42    let mut decoder = GzDecoder::new(data);
43    let mut decompressed = Vec::new();
44    decoder
45        .read_to_end(&mut decompressed)
46        .map_err(|e| CyaneaError::Compression(e.to_string()))?;
47    Ok(decompressed)
48}
49
50/// Detect the compression algorithm from the magic bytes of `data`.
51///
52/// Returns `None` if the data does not match a known format.
53pub fn detect_algorithm(data: &[u8]) -> Option<Algorithm> {
54    if data.len() >= 4 && data[..4] == [0x28, 0xB5, 0x2F, 0xFD] {
55        Some(Algorithm::Zstd)
56    } else if data.len() >= 2 && data[..2] == [0x1F, 0x8B] {
57        Some(Algorithm::Gzip)
58    } else {
59        None
60    }
61}
62
63/// Decompress data by auto-detecting the algorithm from magic bytes.
64///
65/// Returns an error if the format is unrecognised.
66pub fn decompress(data: &[u8]) -> Result<Vec<u8>> {
67    match detect_algorithm(data) {
68        Some(Algorithm::Zstd) => zstd_decompress(data),
69        Some(Algorithm::Gzip) => gzip_decompress(data),
70        None => Err(CyaneaError::Compression(
71            "unknown compression format".into(),
72        )),
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_zstd_roundtrip() {
82        let original = b"Hello, world! This is some test data for compression.";
83        let compressed = zstd_compress(original, 3).unwrap();
84        let decompressed = zstd_decompress(&compressed).unwrap();
85        assert_eq!(original.to_vec(), decompressed);
86    }
87
88    #[test]
89    fn test_gzip_roundtrip() {
90        let original = b"Hello, world! This is gzip test data.";
91        let compressed = gzip_compress(original, 6).unwrap();
92        let decompressed = gzip_decompress(&compressed).unwrap();
93        assert_eq!(original.to_vec(), decompressed);
94    }
95
96    #[test]
97    fn test_detect_zstd() {
98        let compressed = zstd_compress(b"test", 3).unwrap();
99        assert_eq!(detect_algorithm(&compressed), Some(Algorithm::Zstd));
100    }
101
102    #[test]
103    fn test_detect_gzip() {
104        let compressed = gzip_compress(b"test", 6).unwrap();
105        assert_eq!(detect_algorithm(&compressed), Some(Algorithm::Gzip));
106    }
107
108    #[test]
109    fn test_detect_unknown() {
110        assert_eq!(detect_algorithm(b"not compressed"), None);
111    }
112
113    #[test]
114    fn test_auto_decompress_zstd() {
115        let original = b"auto-detect zstd";
116        let compressed = zstd_compress(original, 3).unwrap();
117        let decompressed = decompress(&compressed).unwrap();
118        assert_eq!(original.to_vec(), decompressed);
119    }
120
121    #[test]
122    fn test_auto_decompress_gzip() {
123        let original = b"auto-detect gzip";
124        let compressed = gzip_compress(original, 6).unwrap();
125        let decompressed = decompress(&compressed).unwrap();
126        assert_eq!(original.to_vec(), decompressed);
127    }
128
129    #[test]
130    fn test_auto_decompress_unknown() {
131        let result = decompress(b"not compressed data");
132        assert!(result.is_err());
133    }
134}