1use std::borrow::Cow;
2
3use super::{Data, DataRef, DecodeError, EncodeError, WireType};
4
5#[cfg(feature = "brotli")]
6macro_rules! num_to_enum {
7 (
8 $(#[$meta:meta])*
9 $name:ident($inner:ident in [$min:expr, $max:literal]):$exp:literal:$short:literal {
10 $(
11 $(#[$value_meta:meta])*
12 $value:literal
13 ), +$(,)?
14 }
15 ) => {
16 paste::paste! {
17 $(#[$meta])*
18 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::IsVariant)]
19 #[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
20 #[non_exhaustive]
21 #[repr($inner)]
22 pub enum $name {
23 $(
24 #[doc = $exp " " $value "."]
25 $(#[$value_meta])*
26 [< $short:camel $value >] = $value,
27 )*
28 }
29
30 impl $name {
31 #[inline]
33 pub const fn max() -> Self {
34 Self::[< $short:camel $max >]
35 }
36
37 #[inline]
39 pub const fn min() -> Self {
40 Self::[< $short:camel $min >]
41 }
42 }
43
44 impl $name {
45 #[allow(unused_comparisons)]
46 pub(super) const fn [< from_ $inner>](value: $inner) -> Self {
47 match value {
48 $(
49 $value => Self::[< $short:camel $value >],
50 )*
51 val if val > $max => Self::[< $short:camel $max >],
52 val if val < $min => Self::[< $short:camel $min >],
53 _ => Self::[< $short:camel $max >],
54 }
55 }
56 }
57
58 impl From<$inner> for $name {
59 fn from(value: $inner) -> Self {
60 Self::from_u8(value as $inner)
61 }
62 }
63
64 impl From<$name> for $inner {
65 fn from(value: $name) -> Self {
66 value as $inner
67 }
68 }
69
70 #[cfg(feature = "serde")]
71 const _: () = {
72 use serde::{Deserialize, Serialize};
73
74 impl Serialize for $name {
75 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: serde::Serializer,
78 {
79 serializer.[< serialize_ $inner>](*self as $inner)
80 }
81 }
82
83 impl<'de> Deserialize<'de> for $name {
84 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85 where
86 D: serde::Deserializer<'de> {
87 <$inner>::deserialize(deserializer).map(Self::[< from_ $inner >])
88 }
89 }
90 };
91 }
92 };
93}
94
95#[allow(unused)]
96const ZSTD_TAG: u8 = 1;
97#[allow(unused)]
98const BROTLI_TAG: u8 = 2;
99#[allow(unused)]
100const LZ4_TAG: u8 = 3;
101#[allow(unused)]
102const SNAPPY_TAG: u8 = 4;
103
104#[cfg(feature = "brotli")]
105mod brotli_impl;
106mod error;
107mod from_str;
108#[cfg(feature = "zstd")]
109mod zstd_impl;
110
111#[cfg(feature = "brotli")]
112pub use brotli_impl::*;
113pub use error::*;
114pub use from_str::*;
115
116#[cfg(feature = "zstd")]
117pub use zstd_impl::*;
118
119#[cfg(any(feature = "quickcheck", test))]
120mod quickcheck_impl;
121
122#[cfg(feature = "brotli")]
123const BROTLI_BUFFER_SIZE: usize = 4096;
124
125#[cfg(feature = "brotli")]
126const _: () = {
127 impl CompressionError {
128 #[inline]
129 const fn brotli_compress_error(err: std::io::Error) -> Self {
130 Self::Compress(CompressError::Brotli(err))
131 }
132
133 #[inline]
134 const fn brotli_decompress_error(err: std::io::Error) -> Self {
135 Self::Decompress(DecompressError::Brotli(err))
136 }
137 }
138};
139
140#[cfg(feature = "snappy")]
141const _: () = {
142 impl CompressionError {
143 #[inline]
144 const fn snappy_compress_error(err: snap::Error) -> Self {
145 Self::Compress(CompressError::Snappy(err))
146 }
147
148 #[inline]
149 const fn snappy_decompress_error(err: snap::Error) -> Self {
150 Self::Decompress(DecompressError::Snappy(err))
151 }
152 }
153};
154
155#[cfg(feature = "lz4")]
156const _: () = {
157 impl CompressionError {
158 #[inline]
159 const fn lz4_decompress_error(err: lz4_flex::block::DecompressError) -> Self {
160 Self::Decompress(DecompressError::Lz4(err))
161 }
162
163 #[inline]
164 const fn lz4_compress_error(err: lz4_flex::block::CompressError) -> Self {
165 Self::Compress(CompressError::Lz4(err))
166 }
167 }
168};
169
170#[cfg(feature = "zstd")]
171const _: () = {
172 impl CompressionError {
173 #[inline]
174 const fn zstd_compress_error(err: std::io::Error) -> Self {
175 Self::Compress(CompressError::Zstd(err))
176 }
177
178 #[inline]
179 const fn zstd_decompress_error(err: std::io::Error) -> Self {
180 Self::Decompress(DecompressError::Zstd(err))
181 }
182 }
183};
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::IsVariant, derive_more::Display)]
187#[non_exhaustive]
188pub enum CompressAlgorithm {
189 #[display("brotli{_0}")]
191 #[cfg(feature = "brotli")]
192 #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
193 Brotli(BrotliAlgorithm),
194 #[display("lz4")]
196 #[cfg(feature = "lz4")]
197 #[cfg_attr(docsrs, doc(cfg(feature = "lz4")))]
198 Lz4,
199 #[display("snappy")]
201 #[cfg(feature = "snappy")]
202 #[cfg_attr(docsrs, doc(cfg(feature = "snappy")))]
203 Snappy,
204 #[display("zstd({_0})")]
206 #[cfg(feature = "zstd")]
207 #[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
208 Zstd(ZstdCompressionLevel),
209 #[display("unknown({_0})")]
211 Unknown(u8),
212}
213
214#[cfg(any(feature = "quickcheck", test))]
215const _: () = {
216 use quickcheck::Arbitrary;
217
218 impl CompressAlgorithm {
219 const MAX: u8 = SNAPPY_TAG;
220 const MIN: u8 = ZSTD_TAG;
221 }
222
223 impl Arbitrary for CompressAlgorithm {
224 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
225 let val = (u8::arbitrary(g) % Self::MAX) + Self::MIN;
226 match val {
227 #[cfg(feature = "zstd")]
228 ZSTD_TAG => Self::Zstd(ZstdCompressionLevel::arbitrary(g)),
229 #[cfg(feature = "brotli")]
230 BROTLI_TAG => Self::Brotli(BrotliAlgorithm::arbitrary(g)),
231 #[cfg(feature = "lz4")]
232 LZ4_TAG => Self::Lz4,
233 #[cfg(feature = "snappy")]
234 SNAPPY_TAG => Self::Snappy,
235 _ => unreachable!(),
236 }
237 }
238 }
239};
240
241#[allow(clippy::derivable_impls)]
242impl Default for CompressAlgorithm {
243 fn default() -> Self {
244 cfg_if::cfg_if! {
245 if #[cfg(feature = "snappy")] {
246 Self::Snappy
247 } else if #[cfg(feature = "lz4")] {
248 Self::Lz4
249 } else if #[cfg(feature = "brotli")] {
250 Self::Brotli(BrotliAlgorithm::default())
251 } else if #[cfg(feature = "zstd")] {
252 Self::Zstd(ZstdCompressionLevel::default())
253 } else {
254 Self::Unknown(u8::MAX)
255 }
256 }
257 }
258}
259
260impl CompressAlgorithm {
261 pub fn decompress_to(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, CompressionError> {
263 match self {
264 #[cfg(feature = "brotli")]
265 Self::Brotli(_) => {
266 let mut reader = brotli::Decompressor::new(src, BROTLI_BUFFER_SIZE);
267 std::io::copy(&mut reader, &mut std::io::Cursor::new(dst))
268 .map(|bytes| bytes as usize)
269 .map_err(CompressionError::brotli_decompress_error)
270 }
271 #[cfg(feature = "lz4")]
272 Self::Lz4 => {
273 lz4_flex::decompress_into(src, dst).map_err(CompressionError::lz4_decompress_error)
274 }
275 #[cfg(feature = "snappy")]
276 Self::Snappy => snap::raw::Decoder::new()
277 .decompress(src, dst)
278 .map_err(CompressionError::snappy_decompress_error),
279 #[cfg(feature = "zstd")]
280 Self::Zstd(_) => {
281 let mut decoder = zstd::Decoder::new(std::io::Cursor::new(src))
282 .map_err(CompressionError::zstd_decompress_error)?;
283 std::io::copy(&mut decoder, &mut std::io::Cursor::new(dst))
284 .map(|bytes| bytes as usize)
285 .map_err(CompressionError::zstd_decompress_error)
286 }
287 algo => Err(CompressionError::UnknownAlgorithm(*algo)),
288 }
289 }
290
291 pub fn max_compress_len(&self, input_size: usize) -> Result<usize, CompressionError> {
295 Ok(match self {
296 #[cfg(feature = "brotli")]
297 Self::Brotli(_) => {
298 let num_large_blocks = (input_size >> 4) + 12;
301 let overhead = 2 + (4 * num_large_blocks) + 3 + 1;
302 let result = input_size + overhead;
303 if input_size == 0 {
304 2
305 } else if result < input_size {
306 input_size
307 } else {
308 result
309 }
310 }
311 #[cfg(feature = "lz4")]
312 Self::Lz4 => lz4_flex::block::get_maximum_output_size(input_size),
313 #[cfg(feature = "snappy")]
314 Self::Snappy => snap::raw::max_compress_len(input_size),
315 #[cfg(feature = "zstd")]
316 Self::Zstd(_) => zstd::zstd_safe::compress_bound(input_size),
317 Self::Unknown(_) => return Err(CompressionError::UnknownAlgorithm(*self)),
318 })
319 }
320
321 pub fn compress_to(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, CompressionError> {
325 match self {
326 #[cfg(feature = "brotli")]
327 CompressAlgorithm::Brotli(_algo) => {
328 use std::io::Write;
329
330 let mut compressor = brotli::CompressorWriter::new(
331 std::io::Cursor::new(dst),
332 BROTLI_BUFFER_SIZE,
333 _algo.quality() as u8 as u32,
334 _algo.window() as u8 as u32,
335 );
336
337 compressor
338 .write_all(src)
339 .map(|_| compressor.into_inner().position() as usize)
340 .map_err(CompressionError::brotli_compress_error)
341 }
342 #[cfg(feature = "lz4")]
343 CompressAlgorithm::Lz4 => {
344 lz4_flex::compress_into(src, dst).map_err(CompressionError::lz4_compress_error)
345 }
346 #[cfg(feature = "snappy")]
347 CompressAlgorithm::Snappy => {
348 let mut encoder = snap::raw::Encoder::new();
349 encoder
350 .compress(src, dst)
351 .map_err(CompressionError::snappy_compress_error)
352 }
353 #[cfg(feature = "zstd")]
354 CompressAlgorithm::Zstd(_level) => {
355 let mut cursor = std::io::Cursor::new(dst);
356 zstd::stream::copy_encode(src, &mut cursor, _level.level() as i32)
357 .map(|_| cursor.position() as usize)
358 .map_err(CompressionError::zstd_compress_error)
359 }
360 algo => Err(CompressionError::UnknownAlgorithm(*algo)),
361 }
362 }
363
364 #[inline]
366 pub fn as_str(&self) -> Cow<'_, str> {
367 match self {
368 #[cfg(feature = "brotli")]
369 Self::Brotli(algo) => return Cow::Owned(format!("brotli{algo}")),
370 #[cfg(feature = "lz4")]
371 Self::Lz4 => "lz4",
372 #[cfg(feature = "snappy")]
373 Self::Snappy => "snappy",
374 #[cfg(feature = "zstd")]
375 Self::Zstd(val) => return Cow::Owned(format!("zstd({})", val.level())),
376 Self::Unknown(val) => return Cow::Owned(format!("unknown({val})")),
377 }
378 .into()
379 }
380
381 #[inline]
385 pub(super) const fn encode_to_u16(&self) -> u16 {
386 let (tag, extra) = match self {
387 #[cfg(feature = "brotli")]
388 Self::Brotli(algo) => (BROTLI_TAG, algo.encode()),
389 #[cfg(feature = "lz4")]
390 Self::Lz4 => (LZ4_TAG, 0),
391 #[cfg(feature = "snappy")]
392 Self::Snappy => (SNAPPY_TAG, 0),
393 #[cfg(feature = "zstd")]
394 Self::Zstd(algo) => (ZSTD_TAG, algo.level() as u8),
395 Self::Unknown(v) => (*v, 0),
396 };
397 ((tag as u16) << 8) | (extra as u16)
398 }
399
400 #[inline]
404 pub(super) const fn decode_from_u16(value: u16) -> Self {
405 let tag = (value >> 8) as u8;
406 #[cfg(any(feature = "brotli", feature = "zstd"))]
407 let extra = value as u8;
408
409 match tag {
410 #[cfg(feature = "brotli")]
411 BROTLI_TAG => Self::Brotli(BrotliAlgorithm::decode(extra)),
412 #[cfg(feature = "lz4")]
413 LZ4_TAG => Self::Lz4,
414 #[cfg(feature = "snappy")]
415 SNAPPY_TAG => Self::Snappy,
416 #[cfg(feature = "zstd")]
417 ZSTD_TAG => Self::Zstd(ZstdCompressionLevel::with_level(extra as i8)),
418 v => Self::Unknown(v),
419 }
420 }
421
422 pub(crate) const fn unknown_or_disabled(&self) -> Option<CompressionError> {
423 match *self {
424 #[cfg(feature = "brotli")]
425 Self::Brotli(_) => None,
426 #[cfg(feature = "lz4")]
427 Self::Lz4 => None,
428 #[cfg(feature = "snappy")]
429 Self::Snappy => None,
430 #[cfg(feature = "zstd")]
431 Self::Zstd(_) => None,
432 Self::Unknown(value) => Some({
433 #[cfg(not(all(
434 feature = "brotli",
435 feature = "lz4",
436 feature = "snappy",
437 feature = "zstd"
438 )))]
439 {
440 match value {
441 #[cfg(feature = "brotli")]
442 BROTLI_TAG => CompressionError::disabled(*self, "brotli"),
443 #[cfg(feature = "lz4")]
444 LZ4_TAG => CompressionError::disabled(*self, "lz4"),
445 #[cfg(feature = "snappy")]
446 SNAPPY_TAG => CompressionError::disabled(*self, "snappy"),
447 #[cfg(feature = "zstd")]
448 ZSTD_TAG => CompressionError::disabled(*self, "zstd"),
449 _ => CompressionError::UnknownAlgorithm(*self),
450 }
451 }
452
453 #[cfg(all(
454 feature = "brotli",
455 feature = "lz4",
456 feature = "snappy",
457 feature = "zstd"
458 ))]
459 CompressionError::UnknownAlgorithm(Self::Unknown(value))
460 }),
461 }
462 }
463}
464
465impl From<u16> for CompressAlgorithm {
466 fn from(value: u16) -> Self {
467 Self::decode_from_u16(value)
468 }
469}
470
471impl<'a> DataRef<'a, Self> for CompressAlgorithm {
472 fn decode(buf: &'a [u8]) -> Result<(usize, Self), DecodeError>
473 where
474 Self: Sized,
475 {
476 <u16 as DataRef<u16>>::decode(buf).map(|(bytes_read, value)| (bytes_read, Self::from(value)))
477 }
478}
479
480impl Data for CompressAlgorithm {
481 const WIRE_TYPE: WireType = WireType::Varint;
482
483 type Ref<'a> = Self;
484
485 fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError>
486 where
487 Self: Sized,
488 {
489 Ok(val)
490 }
491
492 fn encoded_len(&self) -> usize {
493 self.encode_to_u16().encoded_len()
494 }
495
496 fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
497 self.encode_to_u16().encode(buf)
498 }
499}
500
501#[inline]
502#[allow(unused)]
503fn parse_or_default<I, O>(s: &str) -> Result<O, I::Err>
504where
505 I: core::str::FromStr,
506 O: Default + From<I>,
507{
508 match s {
509 "" | "-" | "_" => Ok(O::default()),
510 val => val.parse::<I>().map(Into::into),
511 }
512}
513
514#[cfg(feature = "serde")]
515const _: () = {
516 use serde::{Deserialize, Deserializer, Serialize, Serializer};
517
518 #[derive(serde::Serialize, serde::Deserialize)]
519 #[serde(rename_all = "snake_case")]
520 enum CompressAlgorithmHelper {
521 #[cfg(feature = "brotli")]
523 Brotli(BrotliAlgorithm),
524 #[cfg(feature = "lz4")]
526 Lz4,
527 #[cfg(feature = "snappy")]
529 Snappy,
530 #[cfg(feature = "zstd")]
532 Zstd(ZstdCompressionLevel),
533 Unknown(u8),
535 }
536
537 impl From<CompressAlgorithm> for CompressAlgorithmHelper {
538 fn from(algo: CompressAlgorithm) -> Self {
539 match algo {
540 #[cfg(feature = "brotli")]
541 CompressAlgorithm::Brotli(algo) => Self::Brotli(algo),
542 #[cfg(feature = "lz4")]
543 CompressAlgorithm::Lz4 => Self::Lz4,
544 #[cfg(feature = "snappy")]
545 CompressAlgorithm::Snappy => Self::Snappy,
546 #[cfg(feature = "zstd")]
547 CompressAlgorithm::Zstd(algo) => Self::Zstd(algo),
548 CompressAlgorithm::Unknown(v) => Self::Unknown(v),
549 }
550 }
551 }
552
553 impl From<CompressAlgorithmHelper> for CompressAlgorithm {
554 fn from(helper: CompressAlgorithmHelper) -> Self {
555 match helper {
556 #[cfg(feature = "brotli")]
557 CompressAlgorithmHelper::Brotli(algo) => Self::Brotli(algo),
558 #[cfg(feature = "lz4")]
559 CompressAlgorithmHelper::Lz4 => Self::Lz4,
560 #[cfg(feature = "snappy")]
561 CompressAlgorithmHelper::Snappy => Self::Snappy,
562 #[cfg(feature = "zstd")]
563 CompressAlgorithmHelper::Zstd(algo) => Self::Zstd(algo),
564 CompressAlgorithmHelper::Unknown(v) => Self::Unknown(v),
565 }
566 }
567 }
568
569 impl Serialize for CompressAlgorithm {
570 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
571 where
572 S: Serializer,
573 {
574 if serializer.is_human_readable() {
575 CompressAlgorithmHelper::from(*self).serialize(serializer)
576 } else {
577 serializer.serialize_u16(self.encode_to_u16())
578 }
579 }
580 }
581
582 impl<'de> Deserialize<'de> for CompressAlgorithm {
583 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
584 where
585 D: Deserializer<'de>,
586 {
587 if deserializer.is_human_readable() {
588 CompressAlgorithmHelper::deserialize(deserializer).map(Into::into)
589 } else {
590 u16::deserialize(deserializer).map(Self::decode_from_u16)
591 }
592 }
593 }
594};
595
596#[cfg(test)]
597mod tests {
598 use super::CompressAlgorithm;
599
600 #[quickcheck_macros::quickcheck]
601 #[cfg(feature = "serde")]
602 fn compress_algorithm_serde(algo: CompressAlgorithm) -> bool {
603 use bincode::config::standard;
604
605 let Ok(serialized) = serde_json::to_string(&algo) else {
606 return false;
607 };
608 let Ok(deserialized) = serde_json::from_str(&serialized) else {
609 return false;
610 };
611 if algo != deserialized {
612 return false;
613 }
614
615 let Ok(serialized) = bincode::serde::encode_to_vec(algo, standard()) else {
616 return false;
617 };
618
619 let Ok((deserialized, _)) = bincode::serde::decode_from_slice(&serialized, standard()) else {
620 return false;
621 };
622
623 algo == deserialized
624 }
625
626 #[quickcheck_macros::quickcheck]
627 #[cfg(feature = "lz4")]
628 fn lz4(data: Vec<u8>) -> bool {
629 let algo = CompressAlgorithm::Lz4;
630
631 let uncompressed_data_len = data.len();
632 let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
633 let mut buffer = vec![0; max_compress_len];
634 let written = algo.compress_to(&data, &mut buffer).unwrap();
635 assert!(written <= max_compress_len);
636 let mut orig = vec![0; uncompressed_data_len];
637 let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
638 data == orig && uncompressed_data_len == decompressed
639 }
640
641 #[quickcheck_macros::quickcheck]
642 #[cfg(feature = "brotli")]
643 fn brotli(data: Vec<u8>) -> bool {
644 let algo = CompressAlgorithm::Brotli(Default::default());
645 let uncompressed_data_len = data.len();
646 let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
647 let mut buffer = vec![0; max_compress_len];
648 let written = algo.compress_to(&data, &mut buffer).unwrap();
649 assert!(written <= max_compress_len);
650 let mut orig = vec![0; uncompressed_data_len];
651 let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
652 data == orig && uncompressed_data_len == decompressed
653 }
654
655 #[quickcheck_macros::quickcheck]
656 #[cfg(feature = "zstd")]
657 fn zstd(data: Vec<u8>) -> bool {
658 let algo = CompressAlgorithm::Zstd(Default::default());
659 let uncompressed_data_len = data.len();
660 let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
661 let mut buffer = vec![0; max_compress_len];
662 let written = algo.compress_to(&data, &mut buffer).unwrap();
663 assert!(written <= max_compress_len);
664 let mut orig = vec![0; uncompressed_data_len];
665 let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
666 data == orig && uncompressed_data_len == decompressed
667 }
668
669 #[quickcheck_macros::quickcheck]
670 #[cfg(feature = "snappy")]
671 fn snappy(data: Vec<u8>) -> bool {
672 let algo = CompressAlgorithm::Snappy;
673 let uncompressed_data_len = data.len();
674 let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
675 let mut buffer = vec![0; max_compress_len];
676 let written = algo.compress_to(&data, &mut buffer).unwrap();
677 assert!(written <= max_compress_len);
678 let mut orig = vec![0; uncompressed_data_len];
679 let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
680 data == orig && uncompressed_data_len == decompressed
681 }
682
683 #[cfg(feature = "snappy")]
684 #[test]
685 fn max_snappy_output_size() {
686 let algo = CompressAlgorithm::Snappy;
687 let max_compress_len = algo.max_compress_len(4096).unwrap();
688 println!("max_compress_len: {}", max_compress_len);
689 assert!(max_compress_len >= 4096);
690 }
691
692 #[cfg(feature = "lz4")]
693 #[test]
694 fn max_lz4_output_size() {
695 let algo = CompressAlgorithm::Lz4;
696 let max_compress_len = algo.max_compress_len(4096).unwrap();
697 println!("max_compress_len: {}", max_compress_len);
698 assert!(max_compress_len >= 4096);
699 }
700
701 #[cfg(feature = "brotli")]
702 #[test]
703 fn max_brotli_output_size() {
704 let algo = CompressAlgorithm::Brotli(Default::default());
705 let max_compress_len = algo.max_compress_len(4096).unwrap();
706 println!("max_compress_len: {}", max_compress_len);
707 assert!(max_compress_len >= 4096);
708 }
709
710 #[cfg(feature = "zstd")]
711 #[test]
712 fn max_zstd_output_size() {
713 let algo = CompressAlgorithm::Zstd(Default::default());
714 let max_compress_len = algo.max_compress_len(4096).unwrap();
715 println!("max_compress_len: {}", max_compress_len);
716 assert!(max_compress_len >= 4096);
717 }
718}