use derive_more::Display;
use snap::raw::{Decoder, Encoder};
use std::convert::{From, TryInto};
use std::error::Error;
use std::fmt;
use std::io;
use std::result;
type Result<T> = result::Result<T, CompressionError>;
pub const LZ4: &str = "lz4";
pub const SNAPPY: &str = "snappy";
#[derive(Debug)]
pub enum CompressionError {
Snappy(snap::Error),
Lz4(io::Error),
}
impl fmt::Display for CompressionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CompressionError::Snappy(ref err) => write!(f, "Snappy Error: {err:?}"),
CompressionError::Lz4(ref err) => write!(f, "Lz4 Error: {err:?}"),
}
}
}
impl Error for CompressionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
CompressionError::Snappy(ref err) => Some(err),
CompressionError::Lz4(ref err) => Some(err),
}
}
}
impl Clone for CompressionError {
fn clone(&self) -> Self {
match self {
CompressionError::Snappy(error) => CompressionError::Snappy(error.clone()),
CompressionError::Lz4(error) => CompressionError::Lz4(io::Error::new(
error.kind(),
error
.get_ref()
.map(|error| error.to_string())
.unwrap_or_default(),
)),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, Eq, Ord, PartialOrd, Hash, Display)]
pub enum Compression {
Lz4,
Snappy,
None,
}
impl Compression {
pub fn encode(&self, bytes: &[u8]) -> Result<Vec<u8>> {
match *self {
Compression::Lz4 => Compression::encode_lz4(bytes),
Compression::Snappy => Compression::encode_snappy(bytes),
Compression::None => Ok(bytes.into()),
}
}
#[inline]
pub fn is_compressed(self) -> bool {
self != Compression::None
}
pub fn decode(&self, bytes: Vec<u8>) -> Result<Vec<u8>> {
match *self {
Compression::Lz4 => Compression::decode_lz4(bytes),
Compression::Snappy => Compression::decode_snappy(bytes),
Compression::None => Ok(bytes),
}
}
pub fn as_str(&self) -> Option<&'static str> {
match *self {
Compression::Lz4 => Some(LZ4),
Compression::Snappy => Some(SNAPPY),
Compression::None => None,
}
}
fn encode_snappy(bytes: &[u8]) -> Result<Vec<u8>> {
let mut encoder = Encoder::new();
encoder
.compress_vec(bytes)
.map_err(CompressionError::Snappy)
}
fn decode_snappy(bytes: Vec<u8>) -> Result<Vec<u8>> {
let mut decoder = Decoder::new();
decoder
.decompress_vec(bytes.as_slice())
.map_err(CompressionError::Snappy)
}
fn encode_lz4(bytes: &[u8]) -> Result<Vec<u8>> {
let len = 4 + lz4_flex::block::get_maximum_output_size(bytes.len());
assert!(len <= i32::MAX as usize);
let mut result = vec![0; len];
let len = bytes.len() as i32;
result[..4].copy_from_slice(&len.to_be_bytes());
let compressed_len = lz4_flex::compress_into(bytes, &mut result[4..])
.map_err(|error| CompressionError::Lz4(io::Error::new(io::ErrorKind::Other, error)))?;
result.truncate(4 + compressed_len);
Ok(result)
}
fn decode_lz4(bytes: Vec<u8>) -> Result<Vec<u8>> {
let uncompressed_size =
i32::from_be_bytes(bytes[..4].try_into().map_err(|error| {
CompressionError::Lz4(io::Error::new(io::ErrorKind::Other, error))
})?);
lz4_flex::decompress(&bytes[4..], uncompressed_size as usize)
.map_err(|error| CompressionError::Lz4(io::Error::new(io::ErrorKind::Other, error)))
}
}
impl From<String> for Compression {
fn from(compression_string: String) -> Compression {
Compression::from(compression_string.as_str())
}
}
impl<'a> From<&'a str> for Compression {
fn from(compression_str: &'a str) -> Compression {
match compression_str {
LZ4 => Compression::Lz4,
SNAPPY => Compression::Snappy,
_ => Compression::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compression_from_str() {
let lz4 = "lz4";
assert_eq!(Compression::from(lz4), Compression::Lz4);
let snappy = "snappy";
assert_eq!(Compression::from(snappy), Compression::Snappy);
let none = "x";
assert_eq!(Compression::from(none), Compression::None);
}
#[test]
fn test_compression_from_string() {
let lz4 = "lz4".to_string();
assert_eq!(Compression::from(lz4), Compression::Lz4);
let snappy = "snappy".to_string();
assert_eq!(Compression::from(snappy), Compression::Snappy);
let none = "x".to_string();
assert_eq!(Compression::from(none), Compression::None);
}
#[test]
fn test_compression_encode_snappy() {
let snappy_compression = Compression::Snappy;
let bytes = String::from("Hello World").into_bytes().to_vec();
snappy_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_snappy() {
let snappy_compression = Compression::Snappy;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = snappy_compression.encode(&bytes).unwrap();
assert_eq!(snappy_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_encode_lz4() {
let snappy_compression = Compression::Lz4;
let bytes = String::from("Hello World").into_bytes().to_vec();
snappy_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_lz4() {
let lz4_compression = Compression::Lz4;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = lz4_compression.encode(&bytes).unwrap();
assert_eq!(lz4_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_encode_none() {
let none_compression = Compression::None;
let bytes = String::from("Hello World").into_bytes().to_vec();
none_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_none() {
let none_compression = Compression::None;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = none_compression.encode(&bytes).unwrap();
assert_eq!(none_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_decode_lz4_with_invalid_input() {
let lz4_compression = Compression::Lz4;
let decode = lz4_compression.decode(vec![0, 0, 0, 0x7f]);
assert!(decode.is_err());
}
#[test]
fn test_compression_encode_snappy_with_non_utf8() {
let snappy_compression = Compression::Snappy;
let v = vec![0xff, 0xff];
let encoded = snappy_compression
.encode(&v)
.expect("Should work without exceptions");
assert_eq!(snappy_compression.decode(encoded).unwrap(), v);
}
}