1#![cfg_attr(
13 feature = "experimental",
14 doc = r"
15# Example
16
17```no_run
18use parquet::{basic::Compression, compression::{create_codec, CodecOptionsBuilder}};
19
20let codec_options = CodecOptionsBuilder::default()
21 .set_backward_compatible_lz4(false)
22 .build();
23let mut codec = match create_codec(Compression::SNAPPY, &codec_options) {
24 Ok(Some(codec)) => codec,
25 _ => panic!(),
26};
27
28let data = vec![b'p', b'a', b'r', b'q', b'u', b'e', b't'];
29let mut compressed = vec![];
30codec.compress(&data[..], &mut compressed).unwrap();
31
32let mut output = vec![];
33codec.decompress(&compressed[..], &mut output, None).unwrap();
34
35assert_eq!(output, data);
36```
37"
38)]
39use crate::basic::Compression as CodecType;
40use crate::errors::{ParquetError, Result};
41
42pub trait Codec: Send {
44 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()>;
50
51 fn decompress(
59 &mut self,
60 input_buf: &[u8],
61 output_buf: &mut Vec<u8>,
62 uncompress_size: Option<usize>,
63 ) -> Result<usize>;
64}
65
66#[derive(Debug, PartialEq, Eq)]
68pub struct CodecOptions {
69 backward_compatible_lz4: bool,
71}
72
73impl Default for CodecOptions {
74 fn default() -> Self {
75 CodecOptionsBuilder::default().build()
76 }
77}
78
79pub struct CodecOptionsBuilder {
80 backward_compatible_lz4: bool,
82}
83
84impl Default for CodecOptionsBuilder {
85 fn default() -> Self {
86 Self {
87 backward_compatible_lz4: true,
88 }
89 }
90}
91
92impl CodecOptionsBuilder {
93 pub fn set_backward_compatible_lz4(mut self, value: bool) -> CodecOptionsBuilder {
102 self.backward_compatible_lz4 = value;
103 self
104 }
105
106 pub fn build(self) -> CodecOptions {
107 CodecOptions {
108 backward_compatible_lz4: self.backward_compatible_lz4,
109 }
110 }
111}
112
113pub(crate) trait CompressionLevel<T: std::fmt::Display + std::cmp::PartialOrd> {
115 const MINIMUM_LEVEL: T;
116 const MAXIMUM_LEVEL: T;
117
118 fn is_valid_level(level: T) -> Result<()> {
120 let compression_range = Self::MINIMUM_LEVEL..=Self::MAXIMUM_LEVEL;
121 if compression_range.contains(&level) {
122 Ok(())
123 } else {
124 Err(ParquetError::General(format!(
125 "valid compression range {}..={} exceeded.",
126 compression_range.start(),
127 compression_range.end()
128 )))
129 }
130 }
131}
132
133#[cfg_attr(
137 any(feature = "lz4", test),
138 expect(
139 clippy::used_underscore_binding,
140 reason = "`_options` is only read when the `lz4` feature is on"
141 )
142)]
143pub fn create_codec(codec: CodecType, _options: &CodecOptions) -> Result<Option<Box<dyn Codec>>> {
144 #[cfg_attr(
145 any(
146 test,
147 feature = "brotli",
148 feature = "flate2",
149 feature = "lz4",
150 feature = "snap",
151 feature = "zstd"
152 ),
153 expect(unreachable_code)
154 )]
155 #[cfg_attr(
156 all(
157 not(test),
158 not(all(feature = "brotli", feature = "flate2", feature = "zstd"))
159 ),
160 expect(unused_variables)
161 )]
162 match codec {
163 CodecType::BROTLI(level) => {
164 #[cfg(any(feature = "brotli", test))]
165 return Ok(Some(Box::new(BrotliCodec::new(level))));
166 Err(ParquetError::General(
167 "Disabled feature at compile time: brotli".into(),
168 ))
169 }
170 CodecType::GZIP(level) => {
171 #[cfg(any(feature = "flate2", test))]
172 return Ok(Some(Box::new(GZipCodec::new(level))));
173 Err(ParquetError::General(
174 "Disabled feature at compile time: flate2".into(),
175 ))
176 }
177 CodecType::SNAPPY => {
178 #[cfg(any(feature = "snap", test))]
179 return Ok(Some(Box::new(SnappyCodec::new())));
180 Err(ParquetError::General(
181 "Disabled feature at compile time: snap".into(),
182 ))
183 }
184 CodecType::LZ4 => {
185 #[cfg(any(feature = "lz4", test))]
186 return Ok(Some(Box::new(LZ4HadoopCodec::new(
187 _options.backward_compatible_lz4,
188 ))));
189 Err(ParquetError::General(
190 "Disabled feature at compile time: lz4".into(),
191 ))
192 }
193 CodecType::ZSTD(level) => {
194 #[cfg(any(feature = "zstd", test))]
195 return Ok(Some(Box::new(ZSTDCodec::new(level))));
196 Err(ParquetError::General(
197 "Disabled feature at compile time: zstd".into(),
198 ))
199 }
200 CodecType::LZ4_RAW => {
201 #[cfg(any(feature = "lz4", test))]
202 return Ok(Some(Box::new(LZ4RawCodec::new())));
203 Err(ParquetError::General(
204 "Disabled feature at compile time: lz4".into(),
205 ))
206 }
207 CodecType::UNCOMPRESSED => Ok(None),
208 CodecType::LZO => Err(nyi_err!("The codec type {} is not supported yet", codec)),
209 }
210}
211
212#[cfg(any(feature = "snap", test))]
213mod snappy_codec {
214 use snap::raw::{Decoder, Encoder, decompress_len, max_compress_len};
215
216 use crate::compression::Codec;
217 use crate::errors::Result;
218
219 pub struct SnappyCodec {
221 decoder: Decoder,
222 encoder: Encoder,
223 }
224
225 impl SnappyCodec {
226 pub(crate) fn new() -> Self {
228 Self {
229 decoder: Decoder::new(),
230 encoder: Encoder::new(),
231 }
232 }
233 }
234
235 impl Codec for SnappyCodec {
236 fn decompress(
237 &mut self,
238 input_buf: &[u8],
239 output_buf: &mut Vec<u8>,
240 uncompress_size: Option<usize>,
241 ) -> Result<usize> {
242 let len = match uncompress_size {
243 Some(size) => size,
244 None => decompress_len(input_buf)?,
245 };
246 let offset = output_buf.len();
247 output_buf.resize(offset + len, 0);
248 self.decoder
249 .decompress(input_buf, &mut output_buf[offset..])
250 .map_err(|e| e.into())
251 }
252
253 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
254 let output_buf_len = output_buf.len();
255 let required_len = max_compress_len(input_buf.len());
256 output_buf.resize(output_buf_len + required_len, 0);
257 let n = self
258 .encoder
259 .compress(input_buf, &mut output_buf[output_buf_len..])?;
260 output_buf.truncate(output_buf_len + n);
261 Ok(())
262 }
263 }
264}
265#[cfg(any(feature = "snap", test))]
266pub use snappy_codec::*;
267
268#[cfg(any(feature = "flate2", test))]
269mod gzip_codec {
270
271 use std::io::{Read, Write};
272
273 use flate2::{Compression, read, write};
274
275 use crate::compression::Codec;
276 use crate::errors::Result;
277
278 use super::GzipLevel;
279
280 pub struct GZipCodec {
282 level: GzipLevel,
283 }
284
285 impl GZipCodec {
286 pub(crate) fn new(level: GzipLevel) -> Self {
288 Self { level }
289 }
290 }
291
292 impl Codec for GZipCodec {
293 fn decompress(
294 &mut self,
295 input_buf: &[u8],
296 output_buf: &mut Vec<u8>,
297 _uncompress_size: Option<usize>,
298 ) -> Result<usize> {
299 let mut decoder = read::MultiGzDecoder::new(input_buf);
300 decoder.read_to_end(output_buf).map_err(|e| e.into())
301 }
302
303 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
304 let mut encoder = write::GzEncoder::new(output_buf, Compression::new(self.level.0));
305 encoder.write_all(input_buf)?;
306 encoder.try_finish().map_err(|e| e.into())
307 }
308 }
309}
310#[cfg(any(feature = "flate2", test))]
311pub use gzip_codec::*;
312
313#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
344pub struct GzipLevel(u32);
345
346impl Default for GzipLevel {
347 fn default() -> Self {
348 Self(6)
351 }
352}
353
354impl CompressionLevel<u32> for GzipLevel {
355 const MINIMUM_LEVEL: u32 = 0;
356 const MAXIMUM_LEVEL: u32 = 9;
357}
358
359impl GzipLevel {
360 pub fn try_new(level: u32) -> Result<Self> {
364 Self::is_valid_level(level).map(|()| Self(level))
365 }
366
367 pub fn compression_level(&self) -> u32 {
369 self.0
370 }
371}
372
373#[cfg(any(feature = "brotli", test))]
374mod brotli_codec {
375
376 use std::io::{Read, Write};
377
378 use crate::compression::Codec;
379 use crate::errors::Result;
380
381 use super::BrotliLevel;
382
383 const BROTLI_DEFAULT_BUFFER_SIZE: usize = 4096;
384 const BROTLI_DEFAULT_LG_WINDOW_SIZE: u32 = 22; pub struct BrotliCodec {
388 level: BrotliLevel,
389 }
390
391 impl BrotliCodec {
392 pub(crate) fn new(level: BrotliLevel) -> Self {
394 Self { level }
395 }
396 }
397
398 impl Codec for BrotliCodec {
399 fn decompress(
400 &mut self,
401 input_buf: &[u8],
402 output_buf: &mut Vec<u8>,
403 uncompress_size: Option<usize>,
404 ) -> Result<usize> {
405 let buffer_size = uncompress_size.unwrap_or(BROTLI_DEFAULT_BUFFER_SIZE);
406 brotli::Decompressor::new(input_buf, buffer_size)
407 .read_to_end(output_buf)
408 .map_err(|e| e.into())
409 }
410
411 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
412 let mut encoder = brotli::CompressorWriter::new(
413 output_buf,
414 BROTLI_DEFAULT_BUFFER_SIZE,
415 self.level.0,
416 BROTLI_DEFAULT_LG_WINDOW_SIZE,
417 );
418 encoder.write_all(input_buf)?;
419 encoder.flush().map_err(|e| e.into())
420 }
421 }
422}
423#[cfg(any(feature = "brotli", test))]
424pub use brotli_codec::*;
425
426#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
428pub struct BrotliLevel(u32);
429
430impl Default for BrotliLevel {
431 fn default() -> Self {
432 Self(1)
433 }
434}
435
436impl CompressionLevel<u32> for BrotliLevel {
437 const MINIMUM_LEVEL: u32 = 0;
438 const MAXIMUM_LEVEL: u32 = 11;
439}
440
441impl BrotliLevel {
442 pub fn try_new(level: u32) -> Result<Self> {
446 Self::is_valid_level(level).map(|()| Self(level))
447 }
448
449 pub fn compression_level(&self) -> u32 {
451 self.0
452 }
453}
454
455#[cfg(any(feature = "lz4", test))]
456mod lz4_codec {
457 use std::io::{Read, Write};
458
459 use crate::compression::Codec;
460 use crate::errors::{ParquetError, Result};
461
462 const LZ4_BUFFER_SIZE: usize = 4096;
463
464 pub struct LZ4Codec {}
466
467 impl LZ4Codec {
468 pub(crate) fn new() -> Self {
470 Self {}
471 }
472 }
473
474 impl Codec for LZ4Codec {
475 fn decompress(
476 &mut self,
477 input_buf: &[u8],
478 output_buf: &mut Vec<u8>,
479 _uncompress_size: Option<usize>,
480 ) -> Result<usize> {
481 let mut decoder = lz4_flex::frame::FrameDecoder::new(input_buf);
482 let mut buffer: [u8; LZ4_BUFFER_SIZE] = [0; LZ4_BUFFER_SIZE];
483 let mut total_len = 0;
484 loop {
485 let len = decoder.read(&mut buffer)?;
486 if len == 0 {
487 break;
488 }
489 total_len += len;
490 output_buf.write_all(&buffer[0..len])?;
491 }
492 Ok(total_len)
493 }
494
495 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
496 let mut encoder = lz4_flex::frame::FrameEncoder::new(output_buf);
497 let mut from = 0;
498 loop {
499 let to = std::cmp::min(from + LZ4_BUFFER_SIZE, input_buf.len());
500 encoder.write_all(&input_buf[from..to])?;
501 from += LZ4_BUFFER_SIZE;
502 if from >= input_buf.len() {
503 break;
504 }
505 }
506 match encoder.finish() {
507 Ok(_) => Ok(()),
508 Err(e) => Err(ParquetError::External(Box::new(e))),
509 }
510 }
511 }
512}
513
514#[cfg(all(feature = "experimental", any(feature = "lz4", test)))]
515pub use lz4_codec::*;
516
517#[cfg(any(feature = "zstd", test))]
518mod zstd_codec {
519 use crate::compression::{Codec, ZstdLevel};
520 use crate::errors::Result;
521 use std::io::Cursor;
522
523 pub struct ZSTDCodec {
528 level: ZstdLevel,
529 compressor: Option<zstd::bulk::Compressor<'static>>,
532 decompressor: Option<zstd::bulk::Decompressor<'static>>,
533 }
534
535 impl ZSTDCodec {
536 pub(crate) fn new(level: ZstdLevel) -> Self {
538 Self {
539 level,
540 compressor: None,
541 decompressor: None,
542 }
543 }
544 }
545
546 impl Codec for ZSTDCodec {
547 fn decompress(
548 &mut self,
549 input_buf: &[u8],
550 output_buf: &mut Vec<u8>,
551 uncompress_size: Option<usize>,
552 ) -> Result<usize> {
553 let offset = output_buf.len();
554 let len = uncompress_size
555 .or_else(|| {
556 zstd::zstd_safe::get_frame_content_size(input_buf)
558 .ok()
559 .flatten()
560 .map(|size| size as usize)
561 })
562 .unwrap_or_else(|| input_buf.len().saturating_mul(4));
563 output_buf.reserve(len);
564
565 let mut cursor = Cursor::new(output_buf);
566 cursor.set_position(offset as u64);
567 let len = self
568 .decompressor
569 .get_or_insert_with(|| {
570 zstd::bulk::Decompressor::new().expect("can create zstd decompressor")
571 })
572 .decompress_to_buffer(input_buf, &mut cursor)?;
573 Ok(len)
574 }
575
576 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
577 let offset = output_buf.len();
578 let len = zstd::zstd_safe::compress_bound(input_buf.len());
579 output_buf.reserve(len);
580
581 let mut cursor = Cursor::new(output_buf);
582 cursor.set_position(offset as u64);
583 let _written = self
584 .compressor
585 .get_or_insert_with(|| {
586 zstd::bulk::Compressor::new(self.level.compression_level())
587 .expect("valid zstd compression level")
588 })
589 .compress_to_buffer(input_buf, &mut cursor)?;
590 Ok(())
591 }
592 }
593}
594#[cfg(any(feature = "zstd", test))]
595pub use zstd_codec::*;
596
597#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
599pub struct ZstdLevel(i32);
600
601impl CompressionLevel<i32> for ZstdLevel {
602 const MINIMUM_LEVEL: i32 = -131072;
605 const MAXIMUM_LEVEL: i32 = 22;
606}
607
608impl ZstdLevel {
609 pub fn try_new(level: i32) -> Result<Self> {
613 Self::is_valid_level(level).map(|()| Self(level))
614 }
615
616 pub fn compression_level(&self) -> i32 {
618 self.0
619 }
620}
621
622impl Default for ZstdLevel {
623 fn default() -> Self {
624 Self(1)
625 }
626}
627
628#[cfg(any(feature = "lz4", test))]
629mod lz4_raw_codec {
630 use crate::compression::Codec;
631 use crate::errors::ParquetError;
632 use crate::errors::Result;
633
634 pub struct LZ4RawCodec {}
636
637 impl LZ4RawCodec {
638 pub(crate) fn new() -> Self {
640 Self {}
641 }
642 }
643
644 impl Codec for LZ4RawCodec {
645 fn decompress(
646 &mut self,
647 input_buf: &[u8],
648 output_buf: &mut Vec<u8>,
649 uncompress_size: Option<usize>,
650 ) -> Result<usize> {
651 let offset = output_buf.len();
652 let Some(required_len) = uncompress_size else {
653 return Err(ParquetError::General(
654 "LZ4RawCodec unsupported without uncompress_size".into(),
655 ));
656 };
657 output_buf.resize(offset + required_len, 0);
658 match lz4_flex::block::decompress_into(input_buf, &mut output_buf[offset..]) {
659 Ok(n) => {
660 if n != required_len {
661 return Err(ParquetError::General(
662 "LZ4RawCodec uncompress_size is not the expected one".into(),
663 ));
664 }
665 Ok(n)
666 }
667 Err(e) => Err(ParquetError::External(Box::new(e))),
668 }
669 }
670
671 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
672 let offset = output_buf.len();
673 let required_len = lz4_flex::block::get_maximum_output_size(input_buf.len());
674 output_buf.resize(offset + required_len, 0);
675 match lz4_flex::block::compress_into(input_buf, &mut output_buf[offset..]) {
676 Ok(n) => {
677 output_buf.truncate(offset + n);
678 Ok(())
679 }
680 Err(e) => Err(ParquetError::External(Box::new(e))),
681 }
682 }
683 }
684}
685#[cfg(any(feature = "lz4", test))]
686pub use lz4_raw_codec::*;
687
688#[cfg(any(feature = "lz4", test))]
689mod lz4_hadoop_codec {
690 use crate::compression::Codec;
691 use crate::compression::lz4_codec::LZ4Codec;
692 use crate::compression::lz4_raw_codec::LZ4RawCodec;
693 use crate::errors::{ParquetError, Result};
694 use std::io;
695
696 const SIZE_U32: usize = std::mem::size_of::<u32>();
698
699 const PREFIX_LEN: usize = SIZE_U32 * 2;
701
702 pub struct LZ4HadoopCodec {
704 backward_compatible_lz4: bool,
708 }
709
710 impl LZ4HadoopCodec {
711 pub(crate) fn new(backward_compatible_lz4: bool) -> Self {
713 Self {
714 backward_compatible_lz4,
715 }
716 }
717 }
718
719 fn try_decompress_hadoop(input_buf: &[u8], output_buf: &mut [u8]) -> io::Result<usize> {
724 let mut input_len = input_buf.len();
734 let mut input = input_buf;
735 let mut read_bytes = 0;
736 let mut output_len = output_buf.len();
737 let mut output: &mut [u8] = output_buf;
738 while input_len >= PREFIX_LEN {
739 let mut bytes = [0; SIZE_U32];
740 bytes.copy_from_slice(&input[0..4]);
741 let expected_decompressed_size = u32::from_be_bytes(bytes);
742 let mut bytes = [0; SIZE_U32];
743 bytes.copy_from_slice(&input[4..8]);
744 let expected_compressed_size = u32::from_be_bytes(bytes);
745 input = &input[PREFIX_LEN..];
746 input_len -= PREFIX_LEN;
747
748 if input_len < expected_compressed_size as usize {
749 return Err(io::Error::other("Not enough bytes for Hadoop frame"));
750 }
751
752 if output_len < expected_decompressed_size as usize {
753 return Err(io::Error::other(
754 "Not enough bytes to hold advertised output",
755 ));
756 }
757 let decompressed_size =
758 lz4_flex::decompress_into(&input[..expected_compressed_size as usize], output)
759 .map_err(|e| ParquetError::External(Box::new(e)))?;
760 if decompressed_size != expected_decompressed_size as usize {
761 return Err(io::Error::other("Unexpected decompressed size"));
762 }
763 input_len -= expected_compressed_size as usize;
764 output_len -= expected_decompressed_size as usize;
765 read_bytes += expected_decompressed_size as usize;
766 if input_len > expected_compressed_size as usize {
767 input = &input[expected_compressed_size as usize..];
768 output = &mut output[expected_decompressed_size as usize..];
769 } else {
770 break;
771 }
772 }
773 if input_len == 0 {
774 Ok(read_bytes)
775 } else {
776 Err(io::Error::other("Not all input are consumed"))
777 }
778 }
779
780 impl Codec for LZ4HadoopCodec {
781 fn decompress(
782 &mut self,
783 input_buf: &[u8],
784 output_buf: &mut Vec<u8>,
785 uncompress_size: Option<usize>,
786 ) -> Result<usize> {
787 let output_len = output_buf.len();
788 let Some(required_len) = uncompress_size else {
789 return Err(ParquetError::General(
790 "LZ4HadoopCodec unsupported without uncompress_size".into(),
791 ));
792 };
793 output_buf.resize(output_len + required_len, 0);
794 match try_decompress_hadoop(input_buf, &mut output_buf[output_len..]) {
795 Ok(n) => {
796 if n != required_len {
797 return Err(ParquetError::General(
798 "LZ4HadoopCodec uncompress_size is not the expected one".into(),
799 ));
800 }
801 Ok(n)
802 }
803 Err(e) if !self.backward_compatible_lz4 => Err(e.into()),
804 Err(_) => {
807 output_buf.truncate(output_len);
809 match LZ4Codec::new().decompress(input_buf, output_buf, uncompress_size) {
810 Ok(n) => Ok(n),
811 Err(_) => {
812 output_buf.truncate(output_len);
814 LZ4RawCodec::new().decompress(input_buf, output_buf, uncompress_size)
815 }
816 }
817 }
818 }
819 }
820
821 fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
822 let offset = output_buf.len();
824 output_buf.resize(offset + PREFIX_LEN, 0);
825
826 LZ4RawCodec::new().compress(input_buf, output_buf)?;
828
829 let output_buf = &mut output_buf[offset..];
832 let compressed_size = output_buf.len() - PREFIX_LEN;
833 let compressed_size = compressed_size as u32;
834 let uncompressed_size = input_buf.len() as u32;
835 output_buf[..SIZE_U32].copy_from_slice(&uncompressed_size.to_be_bytes());
836 output_buf[SIZE_U32..PREFIX_LEN].copy_from_slice(&compressed_size.to_be_bytes());
837
838 Ok(())
839 }
840 }
841}
842#[cfg(any(feature = "lz4", test))]
843pub use lz4_hadoop_codec::*;
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848
849 use crate::util::test_common::rand_gen::random_bytes;
850
851 fn test_roundtrip(c: CodecType, data: &[u8], uncompress_size: Option<usize>) {
852 let codec_options = CodecOptionsBuilder::default()
853 .set_backward_compatible_lz4(false)
854 .build();
855 let mut c1 = create_codec(c, &codec_options).unwrap().unwrap();
856 let mut c2 = create_codec(c, &codec_options).unwrap().unwrap();
857
858 let mut compressed = Vec::new();
860 let mut decompressed = Vec::new();
861 c1.compress(data, &mut compressed)
862 .expect("Error when compressing");
863
864 let decompressed_size = c2
866 .decompress(compressed.as_slice(), &mut decompressed, uncompress_size)
867 .expect("Error when decompressing");
868 assert_eq!(data.len(), decompressed_size);
869 assert_eq!(data, decompressed.as_slice());
870
871 decompressed.clear();
872 compressed.clear();
873
874 c2.compress(data, &mut compressed)
876 .expect("Error when compressing");
877
878 let decompressed_size = c1
880 .decompress(compressed.as_slice(), &mut decompressed, uncompress_size)
881 .expect("Error when decompressing");
882 assert_eq!(data.len(), decompressed_size);
883 assert_eq!(data, decompressed.as_slice());
884
885 decompressed.clear();
886 compressed.clear();
887
888 let prefix = &[0xDE, 0xAD, 0xBE, 0xEF];
890 decompressed.extend_from_slice(prefix);
891 compressed.extend_from_slice(prefix);
892
893 c2.compress(data, &mut compressed)
894 .expect("Error when compressing");
895
896 assert_eq!(&compressed[..4], prefix);
897
898 let decompressed_size = c2
899 .decompress(&compressed[4..], &mut decompressed, uncompress_size)
900 .expect("Error when decompressing");
901
902 assert_eq!(data.len(), decompressed_size);
903 assert_eq!(data, &decompressed[4..]);
904 assert_eq!(&decompressed[..4], prefix);
905 }
906
907 fn test_codec_with_size(c: CodecType) {
908 let sizes = vec![100, 10000, 100000];
909 for size in sizes {
910 let data = random_bytes(size);
911 test_roundtrip(c, &data, Some(data.len()));
912 }
913 }
914
915 fn test_codec_without_size(c: CodecType) {
916 let sizes = vec![100, 10000, 100000];
917 for size in sizes {
918 let data = random_bytes(size);
919 test_roundtrip(c, &data, None);
920 }
921 }
922
923 #[test]
924 #[cfg_attr(miri, ignore)] fn test_codec_snappy() {
926 test_codec_with_size(CodecType::SNAPPY);
927 test_codec_without_size(CodecType::SNAPPY);
928 }
929
930 #[test]
931 #[cfg_attr(miri, ignore)] fn test_codec_gzip() {
933 for level in GzipLevel::MINIMUM_LEVEL..=GzipLevel::MAXIMUM_LEVEL {
934 let level = GzipLevel::try_new(level).unwrap();
935 test_codec_with_size(CodecType::GZIP(level));
936 test_codec_without_size(CodecType::GZIP(level));
937 }
938 }
939
940 #[test]
941 #[cfg_attr(miri, ignore)] fn test_codec_brotli() {
943 for level in BrotliLevel::MINIMUM_LEVEL..=BrotliLevel::MAXIMUM_LEVEL {
944 let level = BrotliLevel::try_new(level).unwrap();
945 test_codec_with_size(CodecType::BROTLI(level));
946 test_codec_without_size(CodecType::BROTLI(level));
947 }
948 }
949
950 #[test]
951 #[cfg_attr(miri, ignore)] fn test_codec_lz4() {
953 test_codec_with_size(CodecType::LZ4);
954 }
955
956 #[test]
957 #[cfg_attr(miri, ignore)] fn test_codec_zstd() {
959 for level in
961 std::iter::once(ZstdLevel::MINIMUM_LEVEL).chain(-100..=ZstdLevel::MAXIMUM_LEVEL)
962 {
963 let level = ZstdLevel::try_new(level).unwrap();
964 test_codec_with_size(CodecType::ZSTD(level));
965 test_codec_without_size(CodecType::ZSTD(level));
966 }
967 }
968
969 #[test]
970 #[cfg_attr(miri, ignore)] fn test_codec_lz4_raw() {
972 test_codec_with_size(CodecType::LZ4_RAW);
973 }
974}