use std::fmt;
#[cfg(not(target_arch = "wasm32"))]
mod native {
use std::sync::Mutex;
use bytes::BytesMut;
use zstd::bulk::{Compressor as Encoder, Decompressor as Decoder};
use crate::ports::compress::{CompressionError, Compressor};
pub struct ZstdCompressor {
pub level: i32,
compressor: Mutex<Encoder<'static>>,
decompressor: Mutex<Decoder<'static>>,
}
impl ZstdCompressor {
pub const NAME: &str = "zstd";
pub fn new(level: i32) -> Self {
Self {
level,
compressor: Mutex::new(
Encoder::new(level).expect("failed to create zstd compressor"),
),
decompressor: Mutex::new(
Decoder::new().expect("failed to create zstd decompressor"),
),
}
}
}
impl Compressor for ZstdCompressor {
fn compress(&self, input: &[u8], output: &mut BytesMut) -> Result<(), CompressionError> {
let compressed = self
.compressor
.lock()
.unwrap()
.compress(input)
.map_err(|e| CompressionError::Internal(e.to_string()))?;
output.extend_from_slice(&compressed);
Ok(())
}
fn decompress(&self, input: &[u8], output: &mut BytesMut) -> Result<(), CompressionError> {
let capacity = zstd::zstd_safe::get_frame_content_size(input)
.ok()
.flatten()
.map_or(input.len() * 10, |s| s as usize);
let decompressed = self
.decompressor
.lock()
.unwrap()
.decompress(input, capacity)
.map_err(|_| CompressionError::CorruptPayload {
compressor_name: self.name().to_owned(),
})?;
output.extend_from_slice(&decompressed);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
}
}
#[cfg(target_arch = "wasm32")]
mod wasm {
use std::io::Read;
use bytes::BytesMut;
use ruzstd::decoding::StreamingDecoder;
use ruzstd::encoding::{CompressionLevel, compress_to_vec};
use crate::ports::compress::{CompressionError, Compressor};
pub struct ZstdCompressor {
pub level: i32,
}
impl ZstdCompressor {
pub const NAME: &str = "zstd";
pub fn new(level: i32) -> Self {
Self { level }
}
}
impl Compressor for ZstdCompressor {
fn compress(&self, input: &[u8], output: &mut BytesMut) -> Result<(), CompressionError> {
let level = match self.level {
0 => CompressionLevel::Uncompressed,
1..=3 => CompressionLevel::Fastest,
4..=6 => CompressionLevel::Default,
7..=9 => CompressionLevel::Better,
_ => CompressionLevel::Best,
};
output.extend_from_slice(&compress_to_vec(input, level));
Ok(())
}
fn decompress(&self, input: &[u8], output: &mut BytesMut) -> Result<(), CompressionError> {
let mut decoder = StreamingDecoder::new(input)
.map_err(|e| CompressionError::Internal(format!("zstd frame init: {e}")))?;
let mut buf = Vec::new();
decoder
.read_to_end(&mut buf)
.map_err(|e| CompressionError::Internal(format!("zstd decompress: {e}")))?;
output.extend_from_slice(&buf);
Ok(())
}
fn name(&self) -> &'static str {
Self::NAME
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use native::ZstdCompressor;
#[cfg(target_arch = "wasm32")]
pub use wasm::ZstdCompressor;
impl Default for ZstdCompressor {
fn default() -> Self {
Self::new(3)
}
}
impl Clone for ZstdCompressor {
fn clone(&self) -> Self {
Self::new(self.level)
}
}
#[allow(clippy::missing_fields_in_debug)]
impl fmt::Debug for ZstdCompressor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZstdCompressor")
.field("level", &self.level)
.finish()
}
}