Skip to main content

zstd_complete/encoding/
compression_level.rs

1use core::{convert::TryFrom, fmt};
2
3/// A validated Zstandard compression level.
4///
5/// Levels 1 through 22 select progressively stronger compression. Use the
6/// named constants for common choices and [`CompressionLevel::new`] when a
7/// precise level is required.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct CompressionLevel(u8);
10
11impl CompressionLevel {
12    /// Emit raw blocks without compression.
13    pub const UNCOMPRESSED: Self = Self(0);
14    /// Fast compression, corresponding to level 1.
15    pub const FASTEST: Self = Self(1);
16    /// The recommended default, corresponding to level 3.
17    pub const DEFAULT: Self = Self(3);
18    /// A stronger general-purpose setting, corresponding to level 7.
19    pub const BETTER: Self = Self(7);
20    /// The historical ruzstd "best" profile, corresponding to level 11.
21    pub const BEST: Self = Self(11);
22    /// The strongest supported standard level.
23    pub const MAXIMUM: Self = Self(22);
24
25    // Source-compatible aliases for the enum variants exposed by ruzstd 0.9.
26    #[allow(non_upper_case_globals)]
27    pub const Uncompressed: Self = Self::UNCOMPRESSED;
28    #[allow(non_upper_case_globals)]
29    pub const Fastest: Self = Self::FASTEST;
30    #[allow(non_upper_case_globals)]
31    pub const Default: Self = Self::DEFAULT;
32    #[allow(non_upper_case_globals)]
33    pub const Better: Self = Self::BETTER;
34    #[allow(non_upper_case_globals)]
35    pub const Best: Self = Self::BEST;
36
37    /// Creates a precise standard compression level.
38    pub const fn new(level: u8) -> Result<Self, InvalidCompressionLevel> {
39        if level >= 1 && level <= Self::MAXIMUM.0 {
40            Ok(Self(level))
41        } else {
42            Err(InvalidCompressionLevel {
43                level: level as i32,
44            })
45        }
46    }
47
48    /// Returns the numeric level used by the compressor parameter table.
49    pub const fn get(self) -> u8 {
50        self.0
51    }
52
53    pub(crate) const fn c_level(self) -> i32 {
54        self.0 as i32
55    }
56
57    pub(crate) const fn is_uncompressed(self) -> bool {
58        self.0 == Self::UNCOMPRESSED.0
59    }
60
61    pub(crate) const fn uses_fastest_legacy_profile(self) -> bool {
62        self.0 == Self::FASTEST.0
63    }
64
65    pub(crate) const fn uses_best_legacy_profile(self) -> bool {
66        self.0 >= Self::BEST.0
67    }
68}
69
70impl Default for CompressionLevel {
71    fn default() -> Self {
72        Self::DEFAULT
73    }
74}
75
76impl TryFrom<i32> for CompressionLevel {
77    type Error = InvalidCompressionLevel;
78
79    fn try_from(level: i32) -> Result<Self, Self::Error> {
80        let level = u8::try_from(level).map_err(|_| InvalidCompressionLevel { level })?;
81        Self::new(level).map_err(|_| InvalidCompressionLevel {
82            level: i32::from(level),
83        })
84    }
85}
86
87impl From<CompressionLevel> for i32 {
88    fn from(level: CompressionLevel) -> Self {
89        level.c_level()
90    }
91}
92
93/// Error returned when a numeric compression level is outside 1 through 22.
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
95pub struct InvalidCompressionLevel {
96    level: i32,
97}
98
99impl InvalidCompressionLevel {
100    /// Returns the rejected numeric value.
101    pub const fn level(self) -> i32 {
102        self.level
103    }
104}
105
106impl fmt::Display for InvalidCompressionLevel {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(
109            formatter,
110            "compression level {} is outside the supported range 1..=22",
111            self.level
112        )
113    }
114}
115
116#[cfg(feature = "std")]
117impl std::error::Error for InvalidCompressionLevel {}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn precise_levels_validate_the_public_range() {
125        assert_eq!(CompressionLevel::new(1).unwrap().get(), 1);
126        assert_eq!(CompressionLevel::new(22).unwrap().get(), 22);
127        assert_eq!(CompressionLevel::new(0).unwrap_err().level(), 0);
128        assert_eq!(CompressionLevel::new(23).unwrap_err().level(), 23);
129    }
130
131    #[test]
132    fn named_levels_map_to_expected_values() {
133        assert_eq!(CompressionLevel::FASTEST.get(), 1);
134        assert_eq!(CompressionLevel::DEFAULT.get(), 3);
135        assert_eq!(CompressionLevel::BETTER.get(), 7);
136        assert_eq!(CompressionLevel::BEST.get(), 11);
137        assert_eq!(CompressionLevel::MAXIMUM.get(), 22);
138    }
139}