Skip to main content

zstd_complete/encoding/
compression_level.rs

1use core::{convert::TryFrom, fmt};
2
3/// A validated Zstandard compression level.
4///
5/// Positive levels 1 through 22 select progressively stronger compression.
6/// Negative levels select the Zstandard fast strategy, with the absolute value
7/// controlling the acceleration step.
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct CompressionLevel(i32);
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    /// Largest supported fast-strategy acceleration step.
38    pub const MAX_FAST_ACCELERATION: u32 = 128 * 1024;
39
40    /// Creates a precise positive compression level.
41    pub const fn new(level: u8) -> Result<Self, InvalidCompressionLevel> {
42        if level >= 1 && level as i32 <= Self::MAXIMUM.0 {
43            Ok(Self(level as i32))
44        } else {
45            Err(InvalidCompressionLevel {
46                level: level as i32,
47            })
48        }
49    }
50
51    /// Returns the numeric level used by the compressor parameter table.
52    /// Creates a fast level with an explicit match-search acceleration step.
53    pub const fn fast(acceleration: u32) -> Result<Self, InvalidCompressionLevel> {
54        if acceleration >= 1 && acceleration <= Self::MAX_FAST_ACCELERATION {
55            Ok(Self(-(acceleration as i32)))
56        } else {
57            Err(InvalidCompressionLevel {
58                level: if acceleration > i32::MAX as u32 {
59                    i32::MIN
60                } else {
61                    -(acceleration as i32)
62                },
63            })
64        }
65    }
66
67    /// Returns the standard numeric level. Negative values are fast levels.
68    pub const fn get(self) -> i32 {
69        self.0
70    }
71
72    pub(crate) const fn c_level(self) -> i32 {
73        self.0
74    }
75
76    pub(crate) const fn is_uncompressed(self) -> bool {
77        self.0 == Self::UNCOMPRESSED.0
78    }
79
80    pub(crate) const fn uses_fastest_legacy_profile(self) -> bool {
81        self.0 == Self::FASTEST.0
82    }
83
84    pub(crate) const fn uses_best_legacy_profile(self) -> bool {
85        self.0 >= Self::BEST.0
86    }
87}
88
89impl Default for CompressionLevel {
90    fn default() -> Self {
91        Self::DEFAULT
92    }
93}
94
95impl TryFrom<i32> for CompressionLevel {
96    type Error = InvalidCompressionLevel;
97
98    fn try_from(level: i32) -> Result<Self, Self::Error> {
99        if (-(Self::MAX_FAST_ACCELERATION as i32)..=-1).contains(&level)
100            || (1..=Self::MAXIMUM.0).contains(&level)
101        {
102            Ok(Self(level))
103        } else {
104            Err(InvalidCompressionLevel { level })
105        }
106    }
107}
108
109impl From<CompressionLevel> for i32 {
110    fn from(level: CompressionLevel) -> Self {
111        level.c_level()
112    }
113}
114
115/// Error returned when a numeric compression level is outside the supported
116/// fast or positive ranges.
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
118pub struct InvalidCompressionLevel {
119    level: i32,
120}
121
122impl InvalidCompressionLevel {
123    /// Returns the rejected numeric value.
124    pub const fn level(self) -> i32 {
125        self.level
126    }
127}
128
129impl fmt::Display for InvalidCompressionLevel {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(
132            formatter,
133            "compression level {} is outside -131072..=-1 or 1..=22",
134            self.level,
135        )
136    }
137}
138
139#[cfg(feature = "std")]
140impl std::error::Error for InvalidCompressionLevel {}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn precise_levels_validate_the_public_range() {
148        assert_eq!(CompressionLevel::new(1).unwrap().get(), 1);
149        assert_eq!(CompressionLevel::new(22).unwrap().get(), 22);
150        assert_eq!(CompressionLevel::new(0).unwrap_err().level(), 0);
151        assert_eq!(CompressionLevel::new(23).unwrap_err().level(), 23);
152        assert_eq!(CompressionLevel::fast(1).unwrap().get(), -1);
153        assert_eq!(CompressionLevel::fast(100).unwrap().get(), -100);
154        assert_eq!(
155            CompressionLevel::try_from(-131_072).unwrap().get(),
156            -131_072
157        );
158        assert_eq!(
159            CompressionLevel::try_from(-131_073).unwrap_err().level(),
160            -131_073
161        );
162        assert_eq!(CompressionLevel::fast(0).unwrap_err().level(), 0);
163    }
164
165    #[test]
166    fn named_levels_map_to_expected_values() {
167        assert_eq!(CompressionLevel::FASTEST.get(), 1);
168        assert_eq!(CompressionLevel::DEFAULT.get(), 3);
169        assert_eq!(CompressionLevel::BETTER.get(), 7);
170        assert_eq!(CompressionLevel::BEST.get(), 11);
171        assert_eq!(CompressionLevel::MAXIMUM.get(), 22);
172    }
173}