Skip to main content

rawshift_image/
error.rs

1//! Error types for RAW image processing.
2//!
3//! This module defines comprehensive error types for TIFF parsing,
4//! format-specific errors, and I/O errors.
5//!
6//! Errors are organized into categories:
7//! - [`ParseError`] — TIFF/binary parse issues
8//! - [`FormatError`] — Format-specific decode failures
9//! - [`ProcessingError`] — Demosaic, color, tonemap
10//! - [`EncodeError`] — Output encoding
11//! - [`RawError::Unsupported`] — Feature not implemented
12
13use std::io;
14use thiserror::Error;
15
16use crate::core::BitDepth;
17#[cfg(feature = "tiff-parser")]
18use crate::tiff::TiffTag;
19
20/// Main error type for the rawshift library.
21#[derive(Debug, Error)]
22pub enum RawError {
23    /// I/O error during file operations.
24    #[error("I/O error: {0}")]
25    Io(#[from] io::Error),
26
27    /// TIFF/binary parse error.
28    #[error(transparent)]
29    Parse(#[from] ParseError),
30
31    /// Format-specific decode error.
32    #[error(transparent)]
33    Format(#[from] FormatError),
34
35    /// Processing pipeline error.
36    #[error(transparent)]
37    Processing(#[from] ProcessingError),
38
39    /// Output encoding error.
40    #[error(transparent)]
41    Encode(#[from] EncodeError),
42
43    /// Feature not yet implemented.
44    #[error("Unsupported: {0}")]
45    Unsupported(String),
46}
47
48/// TIFF and binary parse errors.
49#[derive(Debug, Error)]
50pub enum ParseError {
51    /// Invalid TIFF magic number.
52    #[error("Invalid TIFF magic number: expected {expected}, found {found}")]
53    InvalidMagic {
54        /// Expected magic number
55        expected: u16,
56        /// Actual magic number found
57        found: u16,
58    },
59
60    /// Invalid byte order marker.
61    #[error("Invalid byte order marker: 0x{0:04X} (expected 'II' or 'MM')")]
62    InvalidByteOrder(u16),
63
64    /// Invalid or malformed IFD.
65    #[error("Invalid IFD at offset {offset}: {reason}")]
66    InvalidIfd {
67        /// Offset where the IFD was expected
68        offset: u64,
69        /// Description of what's wrong
70        reason: String,
71    },
72
73    /// Required tag not found.
74    #[cfg(feature = "tiff-parser")]
75    #[error("Required tag not found: {0}")]
76    TagNotFound(TiffTag),
77
78    /// Offset exceeds file boundaries.
79    #[error("Offset out of bounds: offset {offset} + size {size} exceeds file size {file_size}")]
80    OffsetOutOfBounds {
81        /// The offset that's out of bounds
82        offset: u64,
83        /// Size of data being accessed
84        size: u64,
85        /// Total file size
86        file_size: u64,
87    },
88
89    /// Unknown TIFF data type.
90    #[error("Unknown TIFF data type: {0}")]
91    UnknownDataType(u16),
92
93    /// Invalid image dimensions.
94    #[error("Invalid image dimensions: {width}x{height}")]
95    InvalidDimensions {
96        /// Image width
97        width: u32,
98        /// Image height
99        height: u32,
100    },
101
102    /// Circular reference detected in IFD chain.
103    #[error("Circular reference detected in IFD chain at offset {0}")]
104    CircularReference(u64),
105
106    /// Binary parse error (from binrw or other parsers).
107    #[error("Binary parse error: {0}")]
108    BinaryParse(String),
109}
110
111/// Format-specific decode errors.
112#[derive(Debug, Error)]
113pub enum FormatError {
114    /// Canon CR2 format error.
115    #[cfg(feature = "cr2-decode")]
116    #[error("CR2 error: {0}")]
117    Cr2(String),
118
119    /// Nikon NEF format error.
120    #[cfg(feature = "nef-decode")]
121    #[error("NEF error: {0}")]
122    Nef(String),
123
124    /// Canon CR3/ISOBMFF format error.
125    #[cfg(feature = "cr3-decode")]
126    #[error("CR3 error: {0}")]
127    Cr3(String),
128
129    /// Fujifilm RAF format error.
130    #[cfg(feature = "raf-decode")]
131    #[error("RAF error: {0}")]
132    Raf(String),
133
134    /// Canon CRW/CIFF format error.
135    #[cfg(feature = "crw-decode")]
136    #[error("CRW error: {0}")]
137    Crw(String),
138
139    /// Standard image format decoding error.
140    #[error("Image decode error ({format}): {message}")]
141    ImageDecode {
142        /// Format name (e.g., "JPEG", "PNG")
143        format: &'static str,
144        /// Error description
145        message: String,
146    },
147
148    /// Decompression error.
149    #[error("Decompression error: {0}")]
150    Decompression(String),
151}
152
153/// Processing pipeline errors.
154#[derive(Debug, Error)]
155pub enum ProcessingError {
156    /// Demosaicing error.
157    #[error("Demosaic error: {0}")]
158    Demosaic(String),
159
160    /// Color processing error.
161    #[error("Color processing error: {0}")]
162    Color(String),
163}
164
165/// Output encoding errors.
166///
167/// `#[non_exhaustive]`: new encoder backends may introduce new error variants
168/// without that being a breaking change.
169#[derive(Debug, Error)]
170#[non_exhaustive]
171pub enum EncodeError {
172    /// Generic encoding/export error.
173    #[error("Encoding error ({format}): {message}")]
174    Encoding {
175        /// Format name (e.g., "JPEG", "PNG")
176        format: &'static str,
177        /// Error description
178        message: String,
179    },
180
181    /// The selected encoder does not support the requested output bit depth.
182    #[error("{format} encoder does not support {requested:?} output")]
183    UnsupportedBitDepth {
184        /// Format name (e.g., "JPEG", "AVIF")
185        format: &'static str,
186        /// The bit depth that was requested but is not supported.
187        requested: BitDepth,
188    },
189
190    /// JPEG encoding error.
191    #[cfg(feature = "jpeg-encode")]
192    #[error("JPEG encoding error: {0}")]
193    Jpeg(#[from] jpeg_encoder::EncodingError),
194
195    /// WebP encoding error.
196    #[error("WebP error: {0}")]
197    WebP(String),
198
199    /// JPEG XL (libjxl) encoding error.
200    #[error("JXL error: {0}")]
201    Jxl(String),
202}
203
204#[cfg(feature = "tiff-parser")]
205impl From<binrw::Error> for RawError {
206    fn from(err: binrw::Error) -> Self {
207        RawError::Parse(ParseError::BinaryParse(err.to_string()))
208    }
209}
210
211#[cfg(feature = "jpeg-encode")]
212impl From<jpeg_encoder::EncodingError> for RawError {
213    fn from(err: jpeg_encoder::EncodingError) -> Self {
214        RawError::Encode(EncodeError::Jpeg(err))
215    }
216}
217
218/// Result type alias using RawError.
219pub type RawResult<T> = Result<T, RawError>;
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_error_display() {
227        let err = RawError::Parse(ParseError::InvalidMagic {
228            expected: 42,
229            found: 0,
230        });
231        let s = format!("{}", err);
232        assert!(s.contains("Invalid TIFF magic"));
233
234        #[cfg(feature = "tiff-parser")]
235        {
236            let err = RawError::Parse(ParseError::TagNotFound(crate::tiff::TiffTag::ImageWidth));
237            let s = format!("{}", err);
238            assert!(s.contains("ImageWidth"));
239        }
240    }
241
242    #[test]
243    fn test_io_error_conversion() {
244        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
245        let raw_err: RawError = io_err.into();
246        assert!(matches!(raw_err, RawError::Io(_)));
247    }
248
249    #[test]
250    fn test_parse_error_conversion() {
251        let parse_err = ParseError::InvalidByteOrder(0x1234);
252        let raw_err: RawError = parse_err.into();
253        assert!(matches!(
254            raw_err,
255            RawError::Parse(ParseError::InvalidByteOrder(0x1234))
256        ));
257    }
258
259    #[cfg(feature = "cr2-decode")]
260    #[test]
261    fn test_format_error_conversion() {
262        let fmt_err = FormatError::Cr2("test error".to_string());
263        let raw_err: RawError = fmt_err.into();
264        assert!(matches!(raw_err, RawError::Format(FormatError::Cr2(_))));
265    }
266
267    #[test]
268    fn test_encode_error_conversion() {
269        let enc_err = EncodeError::Encoding {
270            format: "PNG",
271            message: "test".to_string(),
272        };
273        let raw_err: RawError = enc_err.into();
274        assert!(matches!(
275            raw_err,
276            RawError::Encode(EncodeError::Encoding { .. })
277        ));
278    }
279}