Skip to main content

mozjpeg_rs/
error.rs

1//! Error types for the mozjpeg encoder.
2
3use std::fmt;
4
5/// Result type for mozjpeg operations.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Error type for mozjpeg operations.
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Error {
12    /// Invalid image dimensions (zero width or height)
13    InvalidDimensions {
14        /// Image width
15        width: u32,
16        /// Image height
17        height: u32,
18    },
19    /// Image buffer size doesn't match dimensions
20    BufferSizeMismatch {
21        /// Expected buffer size in bytes
22        expected: usize,
23        /// Actual buffer size in bytes
24        actual: usize,
25    },
26    /// Invalid quality value (must be 1-100)
27    InvalidQuality(u8),
28    /// Invalid quantization table index
29    InvalidQuantTableIndex(usize),
30    /// Invalid component index
31    InvalidComponentIndex(usize),
32    /// Invalid Huffman table index
33    InvalidHuffmanTableIndex(usize),
34    /// Invalid sampling factor
35    InvalidSamplingFactor {
36        /// Horizontal sampling factor
37        h: u8,
38        /// Vertical sampling factor
39        v: u8,
40    },
41    /// Invalid scan specification
42    InvalidScanSpec {
43        /// Reason for the invalid specification
44        reason: &'static str,
45    },
46    /// Invalid Huffman table structure
47    InvalidHuffmanTable,
48    /// Huffman code length overflow (exceeds max allowed)
49    HuffmanCodeLengthOverflow,
50    /// Unsupported color space
51    UnsupportedColorSpace,
52    /// Unsupported feature
53    UnsupportedFeature(&'static str),
54    /// Internal encoder error
55    InternalError(&'static str),
56    /// I/O error
57    IoError(String),
58    /// Memory allocation failed
59    AllocationFailed,
60    /// Encoding was cancelled by the caller
61    Cancelled,
62    /// Encoding exceeded the timeout duration
63    TimedOut,
64    /// Image dimensions exceed the configured maximum
65    DimensionLimitExceeded {
66        /// Requested width
67        width: u32,
68        /// Requested height
69        height: u32,
70        /// Maximum allowed width
71        max_width: u32,
72        /// Maximum allowed height
73        max_height: u32,
74    },
75    /// Estimated memory usage exceeds the configured limit
76    AllocationLimitExceeded {
77        /// Estimated memory in bytes
78        estimated: usize,
79        /// Maximum allowed memory in bytes
80        limit: usize,
81    },
82    /// Pixel count (width × height) exceeds the configured limit
83    PixelCountExceeded {
84        /// Actual pixel count
85        pixel_count: u64,
86        /// Maximum allowed pixel count
87        limit: u64,
88    },
89    /// ICC profile size exceeds the configured limit
90    IccProfileTooLarge {
91        /// Actual ICC profile size in bytes
92        size: usize,
93        /// Maximum allowed size in bytes
94        limit: usize,
95    },
96    /// Row stride is too small for the image width
97    InvalidStride {
98        /// Provided stride in bytes
99        stride: usize,
100        /// Minimum required stride in bytes
101        minimum: usize,
102    },
103}
104
105impl fmt::Display for Error {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            Error::InvalidDimensions { width, height } => {
109                write!(f, "Invalid image dimensions: {}x{}", width, height)
110            }
111            Error::BufferSizeMismatch { expected, actual } => {
112                write!(
113                    f,
114                    "Buffer size mismatch: expected {}, got {}",
115                    expected, actual
116                )
117            }
118            Error::InvalidQuality(q) => {
119                write!(f, "Invalid quality value: {} (must be 1-100)", q)
120            }
121            Error::InvalidQuantTableIndex(idx) => {
122                write!(f, "Invalid quantization table index: {}", idx)
123            }
124            Error::InvalidComponentIndex(idx) => {
125                write!(f, "Invalid component index: {}", idx)
126            }
127            Error::InvalidHuffmanTableIndex(idx) => {
128                write!(f, "Invalid Huffman table index: {}", idx)
129            }
130            Error::InvalidSamplingFactor { h, v } => {
131                write!(f, "Invalid sampling factor: {}x{}", h, v)
132            }
133            Error::InvalidScanSpec { reason } => {
134                write!(f, "Invalid scan specification: {}", reason)
135            }
136            Error::InvalidHuffmanTable => {
137                write!(f, "Invalid Huffman table structure")
138            }
139            Error::HuffmanCodeLengthOverflow => {
140                write!(f, "Huffman code length overflow (exceeds 16 bits)")
141            }
142            Error::UnsupportedColorSpace => {
143                write!(f, "Unsupported color space")
144            }
145            Error::UnsupportedFeature(feature) => {
146                write!(f, "Unsupported feature: {}", feature)
147            }
148            Error::InternalError(msg) => {
149                write!(f, "Internal encoder error: {}", msg)
150            }
151            Error::IoError(msg) => {
152                write!(f, "I/O error: {}", msg)
153            }
154            Error::AllocationFailed => {
155                write!(f, "Memory allocation failed")
156            }
157            Error::Cancelled => {
158                write!(f, "Encoding was cancelled")
159            }
160            Error::TimedOut => {
161                write!(f, "Encoding timed out")
162            }
163            Error::DimensionLimitExceeded {
164                width,
165                height,
166                max_width,
167                max_height,
168            } => {
169                write!(
170                    f,
171                    "Image dimensions {}x{} exceed limit {}x{}",
172                    width, height, max_width, max_height
173                )
174            }
175            Error::AllocationLimitExceeded { estimated, limit } => {
176                write!(
177                    f,
178                    "Estimated memory {} bytes exceeds limit {} bytes",
179                    estimated, limit
180                )
181            }
182            Error::PixelCountExceeded { pixel_count, limit } => {
183                write!(f, "Pixel count {} exceeds limit {}", pixel_count, limit)
184            }
185            Error::IccProfileTooLarge { size, limit } => {
186                write!(
187                    f,
188                    "ICC profile size {} bytes exceeds limit {} bytes",
189                    size, limit
190                )
191            }
192            Error::InvalidStride { stride, minimum } => {
193                write!(
194                    f,
195                    "Invalid stride: {} bytes is less than minimum {} bytes",
196                    stride, minimum
197                )
198            }
199        }
200    }
201}
202
203impl std::error::Error for Error {}
204
205impl From<std::io::Error> for Error {
206    fn from(e: std::io::Error) -> Self {
207        Error::IoError(e.to_string())
208    }
209}
210
211impl From<std::collections::TryReserveError> for Error {
212    fn from(_: std::collections::TryReserveError) -> Self {
213        Error::AllocationFailed
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_error_display() {
223        // Test that all error variants format correctly
224        let errors = [
225            (
226                Error::InvalidDimensions {
227                    width: 0,
228                    height: 100,
229                },
230                "Invalid image dimensions: 0x100",
231            ),
232            (
233                Error::BufferSizeMismatch {
234                    expected: 1000,
235                    actual: 500,
236                },
237                "Buffer size mismatch: expected 1000, got 500",
238            ),
239            (
240                Error::InvalidQuality(0),
241                "Invalid quality value: 0 (must be 1-100)",
242            ),
243            (
244                Error::InvalidQuantTableIndex(5),
245                "Invalid quantization table index: 5",
246            ),
247            (
248                Error::InvalidComponentIndex(4),
249                "Invalid component index: 4",
250            ),
251            (
252                Error::InvalidHuffmanTableIndex(8),
253                "Invalid Huffman table index: 8",
254            ),
255            (
256                Error::InvalidSamplingFactor { h: 5, v: 3 },
257                "Invalid sampling factor: 5x3",
258            ),
259            (
260                Error::InvalidScanSpec {
261                    reason: "test reason",
262                },
263                "Invalid scan specification: test reason",
264            ),
265            (
266                Error::InvalidHuffmanTable,
267                "Invalid Huffman table structure",
268            ),
269            (
270                Error::HuffmanCodeLengthOverflow,
271                "Huffman code length overflow (exceeds 16 bits)",
272            ),
273            (Error::UnsupportedColorSpace, "Unsupported color space"),
274            (
275                Error::UnsupportedFeature("arithmetic coding"),
276                "Unsupported feature: arithmetic coding",
277            ),
278            (
279                Error::InternalError("test error"),
280                "Internal encoder error: test error",
281            ),
282            (Error::IoError("disk full".into()), "I/O error: disk full"),
283            (Error::AllocationFailed, "Memory allocation failed"),
284            (Error::Cancelled, "Encoding was cancelled"),
285            (Error::TimedOut, "Encoding timed out"),
286            (
287                Error::DimensionLimitExceeded {
288                    width: 5000,
289                    height: 3000,
290                    max_width: 4096,
291                    max_height: 4096,
292                },
293                "Image dimensions 5000x3000 exceed limit 4096x4096",
294            ),
295            (
296                Error::AllocationLimitExceeded {
297                    estimated: 100_000_000,
298                    limit: 50_000_000,
299                },
300                "Estimated memory 100000000 bytes exceeds limit 50000000 bytes",
301            ),
302            (
303                Error::InvalidStride {
304                    stride: 100,
305                    minimum: 300,
306                },
307                "Invalid stride: 100 bytes is less than minimum 300 bytes",
308            ),
309        ];
310
311        for (error, expected_msg) in errors {
312            assert_eq!(error.to_string(), expected_msg);
313        }
314    }
315
316    #[test]
317    fn test_error_is_error_trait() {
318        let error: &dyn std::error::Error = &Error::InvalidQuality(0);
319        // Just verify it implements Error trait
320        let _ = error.to_string();
321    }
322
323    #[test]
324    fn test_from_io_error() {
325        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
326        let error: Error = io_error.into();
327        assert!(matches!(error, Error::IoError(_)));
328        assert!(error.to_string().contains("file not found"));
329    }
330
331    #[test]
332    fn test_error_clone_and_eq() {
333        let error1 = Error::InvalidQuality(50);
334        let error2 = error1.clone();
335        assert_eq!(error1, error2);
336
337        let error3 = Error::InvalidQuality(60);
338        assert_ne!(error1, error3);
339    }
340}