Skip to main content

alpm_compress/compression/
level.rs

1//! Compression level structs for various compression algorithms.
2
3use std::fmt::{Debug, Display};
4
5use log::trace;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::error::Error;
10
11/// A macro to define a compression level struct.
12///
13/// Accepts the `name` of the compression level struct, its `min`, `max` and `default` values, the
14/// `compression` executable it relates to and a `url`, that defines a man page for the
15/// `compression` executable.
16macro_rules! define_compression_level {
17    (
18        $name:ident,
19        Min => $min:expr,
20        Max => $max:expr,
21        Default => $default:expr,
22        $compression:literal,
23        $url:literal
24    ) => {
25        #[doc = concat!("Compression level for ", $compression, " compression.")]
26        #[derive(Clone, Debug, Eq, PartialEq)]
27        #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28        pub struct $name(u8);
29
30        impl $name {
31            #[doc = concat!("Creates a new [`", stringify!($name), "`] from a [`u8`].")]
32            ///
33            #[doc = concat!("The `level` must be in the range of [`", stringify!($name), "::min`] and [`", stringify!($name), "::max`].")]
34            ///
35            /// # Errors
36            ///
37            #[doc = concat!("Returns an error if the value is not in the range of [`", stringify!($name), "::min`] and [`", stringify!($name), "::max`].")]
38            pub fn new(level: u8) -> Result<Self, Error> {
39                trace!(concat!("Creating new compression level for ", $compression, " compression with {{level}}"));
40                if !($name::min()..=$name::max()).contains(&level) {
41                    return Err(Error::InvalidCompressionLevel {
42                        level,
43                        min: $name::min(),
44                        max: $name::max(),
45                    });
46                }
47                Ok(Self(level))
48            }
49
50            #[doc = concat!("Returns the default level (`", stringify!($default), "`) for [`", stringify!($name), "`].")]
51            ///
52            #[doc = concat!("The default level adheres to the one selected by the [", $compression, "] executable.")]
53            ///
54            #[doc = concat!("[", $compression, "]: ", $url)]
55            pub const fn default_level() -> u8 {
56                $default
57            }
58
59            #[doc = concat!("Returns the minimum allowed level (`", stringify!($min), "`) for [`", stringify!($name), "`].")]
60            pub const fn min() -> u8 {
61                $min
62            }
63
64            #[doc = concat!("Returns the maximum allowed level (`", stringify!($max), "`) for [`", stringify!($name), "`].")]
65            pub const fn max() -> u8 {
66                $max
67            }
68        }
69
70        impl Default for $name {
71            #[doc = concat!("Returns the default [`", stringify!($name), "`].")]
72            ///
73            #[doc = concat!("Delegates to [`", stringify!($name), "::default_level`] for retrieving the default compression level.")]
74            fn default() -> Self {
75                Self($name::default_level())
76            }
77        }
78
79        impl Display for $name {
80            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81                write!(f, "{}", self.0)
82            }
83        }
84
85        impl From<&$name> for i16 {
86            fn from(value: &$name) -> Self {
87                i16::from(value.0)
88            }
89        }
90
91        impl From<&$name> for i32 {
92            fn from(value: &$name) -> Self {
93                i32::from(value.0)
94            }
95        }
96
97        impl From<&$name> for i64 {
98            fn from(value: &$name) -> Self {
99                i64::from(value.0)
100            }
101        }
102
103        impl From<&$name> for i128 {
104            fn from(value: &$name) -> Self {
105                i128::from(value.0)
106            }
107        }
108
109        impl From<&$name> for u16 {
110            fn from(value: &$name) -> Self {
111                 u16::from(value.0)
112            }
113        }
114
115        impl From<&$name> for u32 {
116            fn from(value: &$name) -> Self {
117                 u32::from(value.0)
118            }
119        }
120
121        impl From<&$name> for u64 {
122            fn from(value: &$name) -> Self {
123                 u64::from(value.0)
124            }
125        }
126
127        impl From<&$name> for u128 {
128            fn from(value: &$name) -> Self {
129                 u128::from(value.0)
130            }
131        }
132
133        impl TryFrom<i8> for $name {
134            type Error = Error;
135
136            fn try_from(value: i8) -> Result<Self, Error> {
137                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
138            }
139        }
140
141        impl TryFrom<i16> for $name {
142            type Error = Error;
143
144            fn try_from(value: i16) -> Result<Self, Error> {
145                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
146            }
147        }
148
149        impl TryFrom<i32> for $name {
150            type Error = Error;
151
152            fn try_from(value: i32) -> Result<Self, Error> {
153                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
154            }
155        }
156
157        impl TryFrom<i64> for $name {
158            type Error = Error;
159
160            fn try_from(value: i64) -> Result<Self, Error> {
161                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
162            }
163        }
164
165        impl TryFrom<u8> for $name {
166            type Error = Error;
167
168            fn try_from(value: u8) -> Result<Self, Error> {
169                 $name::new(value)
170            }
171        }
172
173        impl TryFrom<u16> for $name {
174            type Error = Error;
175
176            fn try_from(value: u16) -> Result<Self, Error> {
177                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
178            }
179        }
180
181        impl TryFrom<u32> for $name {
182            type Error = Error;
183
184            fn try_from(value: u32) -> Result<Self, Error> {
185                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
186            }
187        }
188
189        impl TryFrom<u64> for $name {
190            type Error = Error;
191
192            fn try_from(value: u64) -> Result<Self, Error> {
193                 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
194            }
195        }
196    };
197}
198
199// Create the bzip2 compression level struct.
200define_compression_level!(
201    Bzip2CompressionLevel,
202    Min => 1,
203    Max => 9,
204    Default => 9,
205    "bzip2",
206    "https://man.archlinux.org/man/bzip2.1"
207);
208
209// Create the gzip compression level struct.
210define_compression_level!(
211    GzipCompressionLevel,
212    Min => 1,
213    Max => 9,
214    Default => 6,
215    "gzip",
216    "https://man.archlinux.org/man/gzip.1"
217);
218
219// Create the xz compression level struct.
220define_compression_level!(
221    XzCompressionLevel,
222    Min => 0,
223    Max => 9,
224    Default => 6,
225    "xz",
226    "https://man.archlinux.org/man/xz.1"
227);
228
229// Create the zstd compression level struct.
230define_compression_level!(
231    ZstdCompressionLevel,
232    Min => 0,
233    Max => 22,
234    Default => 3,
235    "zstd",
236    "https://man.archlinux.org/man/zstd.1"
237);
238
239#[cfg(test)]
240mod tests {
241    use proptest::{proptest, test_runner::Config as ProptestConfig};
242    use rstest::rstest;
243    use testresult::TestResult;
244
245    use super::*;
246
247    proptest! {
248        #![proptest_config(ProptestConfig::with_cases(1000))]
249
250        #[test]
251        fn valid_bzip2_compression_level_try_from_i8(input in 1..=9i8) {
252            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
253        }
254
255        #[test]
256        fn valid_bzip2_compression_level_try_from_i16(input in 1..=9i16) {
257            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
258        }
259
260        #[test]
261        fn valid_bzip2_compression_level_try_from_i32(input in 1..=9i32) {
262            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
263        }
264
265        #[test]
266        fn valid_bzip2_compression_level_try_from_i64(input in 1..=9i64) {
267            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
268        }
269
270        #[test]
271        fn valid_bzip2_compression_level_try_from_u8(input in 1..=9u8) {
272            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
273        }
274
275        #[test]
276        fn valid_bzip2_compression_level_try_from_u16(input in 1..=9u16) {
277            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
278        }
279
280        #[test]
281        fn valid_bzip2_compression_level_try_from_u32(input in 1..=9u32) {
282            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
283        }
284
285        #[test]
286        fn valid_bzip2_compression_level_try_from_u64(input in 1..=9u64) {
287            assert!(Bzip2CompressionLevel::try_from(input).is_ok());
288        }
289
290        #[test]
291        fn valid_gzip_compression_level_try_from_i8(input in 1..=9i8) {
292            assert!(GzipCompressionLevel::try_from(input).is_ok());
293        }
294
295        #[test]
296        fn valid_gzip_compression_level_try_from_i16(input in 1..=9i16) {
297            assert!(GzipCompressionLevel::try_from(input).is_ok());
298        }
299
300        #[test]
301        fn valid_gzip_compression_level_try_from_i32(input in 1..=9i32) {
302            assert!(GzipCompressionLevel::try_from(input).is_ok());
303        }
304
305        #[test]
306        fn valid_gzip_compression_level_try_from_i64(input in 1..=9i64) {
307            assert!(GzipCompressionLevel::try_from(input).is_ok());
308        }
309
310        #[test]
311        fn valid_gzip_compression_level_try_from_u8(input in 1..=9u8) {
312            assert!(GzipCompressionLevel::try_from(input).is_ok());
313        }
314
315        #[test]
316        fn valid_gzip_compression_level_try_from_u16(input in 1..=9u16) {
317            assert!(GzipCompressionLevel::try_from(input).is_ok());
318        }
319
320        #[test]
321        fn valid_gzip_compression_level_try_from_u32(input in 1..=9u32) {
322            assert!(GzipCompressionLevel::try_from(input).is_ok());
323        }
324
325        #[test]
326        fn valid_gzip_compression_level_try_from_u64(input in 1..=9u64) {
327            assert!(GzipCompressionLevel::try_from(input).is_ok());
328        }
329
330        #[test]
331        fn valid_xz_compression_level_try_from_i8(input in 0..=9i8) {
332            assert!(XzCompressionLevel::try_from(input).is_ok());
333        }
334
335        #[test]
336        fn valid_xz_compression_level_try_from_i16(input in 0..=9i16) {
337            assert!(XzCompressionLevel::try_from(input).is_ok());
338        }
339
340        #[test]
341        fn valid_xz_compression_level_try_from_i32(input in 0..=9i32) {
342            assert!(XzCompressionLevel::try_from(input).is_ok());
343        }
344
345        #[test]
346        fn valid_xz_compression_level_try_from_i64(input in 0..=9i64) {
347            assert!(XzCompressionLevel::try_from(input).is_ok());
348        }
349
350        #[test]
351        fn valid_xz_compression_level_try_from_u8(input in 0..=9u8) {
352            assert!(XzCompressionLevel::try_from(input).is_ok());
353        }
354
355        #[test]
356        fn valid_xz_compression_level_try_from_u16(input in 0..=9u16) {
357            assert!(XzCompressionLevel::try_from(input).is_ok());
358        }
359
360        #[test]
361        fn valid_xz_compression_level_try_from_u32(input in 0..=9u32) {
362            assert!(XzCompressionLevel::try_from(input).is_ok());
363        }
364
365        #[test]
366        fn valid_xz_compression_level_try_from_u64(input in 0..=9u64) {
367            assert!(XzCompressionLevel::try_from(input).is_ok());
368        }
369
370        #[test]
371        fn valid_zstd_compression_level_try_from_i8(input in 0..=22i8) {
372            assert!(ZstdCompressionLevel::try_from(input).is_ok());
373        }
374
375        #[test]
376        fn valid_zstd_compression_level_try_from_i16(input in 0..=22i16) {
377            assert!(ZstdCompressionLevel::try_from(input).is_ok());
378        }
379
380        #[test]
381        fn valid_zstd_compression_level_try_from_i32(input in 0..=22i32) {
382            assert!(ZstdCompressionLevel::try_from(input).is_ok());
383        }
384
385        #[test]
386        fn valid_zstd_compression_level_try_from_i64(input in 0..=22i64) {
387            assert!(ZstdCompressionLevel::try_from(input).is_ok());
388        }
389
390        #[test]
391        fn valid_zstd_compression_level_try_from_u8(input in 0..=22u8) {
392            assert!(ZstdCompressionLevel::try_from(input).is_ok());
393        }
394
395        #[test]
396        fn valid_zstd_compression_level_try_from_u16(input in 0..=22u16) {
397            assert!(ZstdCompressionLevel::try_from(input).is_ok());
398        }
399
400        #[test]
401        fn valid_zstd_compression_level_try_from_u32(input in 0..=22u32) {
402            assert!(ZstdCompressionLevel::try_from(input).is_ok());
403        }
404
405        #[test]
406        fn valid_zstd_compression_level_try_from_u64(input in 0..=22u64) {
407            assert!(ZstdCompressionLevel::try_from(input).is_ok());
408        }
409    }
410
411    #[rstest]
412    #[case::too_large(Bzip2CompressionLevel::max() + 1)]
413    #[case::too_small(Bzip2CompressionLevel::min() - 1)]
414    fn create_bzip2_compression_level_fails(#[case] level: u8) -> TestResult {
415        if let Ok(level) = Bzip2CompressionLevel::new(level) {
416            panic!("Should not have succeeded but created level: {level}");
417        }
418
419        Ok(())
420    }
421
422    #[test]
423    fn create_bzip2_compression_level_succeeds() -> TestResult {
424        if let Err(error) = Bzip2CompressionLevel::new(6) {
425            panic!("Should have succeeded but raised error:\n{error}");
426        }
427
428        Ok(())
429    }
430
431    #[rstest]
432    #[case::too_large(GzipCompressionLevel::max() + 1)]
433    #[case::too_small(GzipCompressionLevel::min() - 1)]
434    fn create_gzip_compression_level_fails(#[case] level: u8) -> TestResult {
435        if let Ok(level) = GzipCompressionLevel::new(level) {
436            panic!("Should not have succeeded but created level: {level}");
437        }
438
439        Ok(())
440    }
441
442    #[test]
443    fn create_gzip_compression_level_succeeds() -> TestResult {
444        if let Err(error) = GzipCompressionLevel::new(6) {
445            panic!("Should have succeeded but raised error:\n{error}");
446        }
447
448        Ok(())
449    }
450
451    #[test]
452    fn create_xz_compression_level_fails() -> TestResult {
453        if let Ok(level) = XzCompressionLevel::new(XzCompressionLevel::max() + 1) {
454            panic!("Should not have succeeded but created level: {level}");
455        }
456
457        Ok(())
458    }
459
460    #[test]
461    fn create_xz_compression_level_succeeds() -> TestResult {
462        if let Err(error) = XzCompressionLevel::new(6) {
463            panic!("Should have succeeded but raised error:\n{error}");
464        }
465
466        Ok(())
467    }
468
469    #[test]
470    fn create_zstd_compression_level_fails() -> TestResult {
471        if let Ok(level) = ZstdCompressionLevel::new(ZstdCompressionLevel::max() + 1) {
472            panic!("Should not have succeeded but created level: {level}");
473        }
474
475        Ok(())
476    }
477
478    #[test]
479    fn create_zstd_compression_level_succeeds() -> TestResult {
480        if let Err(error) = ZstdCompressionLevel::new(6) {
481            panic!("Should have succeeded but raised error:\n{error}");
482        }
483
484        Ok(())
485    }
486}