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
200#[cfg(feature = "tiff-parser")]
201impl From<binrw::Error> for RawError {
202    fn from(err: binrw::Error) -> Self {
203        RawError::Parse(ParseError::BinaryParse(err.to_string()))
204    }
205}
206
207#[cfg(feature = "jpeg-encode")]
208impl From<jpeg_encoder::EncodingError> for RawError {
209    fn from(err: jpeg_encoder::EncodingError) -> Self {
210        RawError::Encode(EncodeError::Jpeg(err))
211    }
212}
213
214/// Result type alias using RawError.
215pub type RawResult<T> = Result<T, RawError>;
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_error_display() {
223        let err = RawError::Parse(ParseError::InvalidMagic {
224            expected: 42,
225            found: 0,
226        });
227        let s = format!("{}", err);
228        assert!(s.contains("Invalid TIFF magic"));
229
230        #[cfg(feature = "tiff-parser")]
231        {
232            let err = RawError::Parse(ParseError::TagNotFound(crate::tiff::TiffTag::ImageWidth));
233            let s = format!("{}", err);
234            assert!(s.contains("ImageWidth"));
235        }
236    }
237
238    #[test]
239    fn test_io_error_conversion() {
240        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
241        let raw_err: RawError = io_err.into();
242        assert!(matches!(raw_err, RawError::Io(_)));
243    }
244
245    #[test]
246    fn test_parse_error_conversion() {
247        let parse_err = ParseError::InvalidByteOrder(0x1234);
248        let raw_err: RawError = parse_err.into();
249        assert!(matches!(
250            raw_err,
251            RawError::Parse(ParseError::InvalidByteOrder(0x1234))
252        ));
253    }
254
255    #[cfg(feature = "cr2-decode")]
256    #[test]
257    fn test_format_error_conversion() {
258        let fmt_err = FormatError::Cr2("test error".to_string());
259        let raw_err: RawError = fmt_err.into();
260        assert!(matches!(raw_err, RawError::Format(FormatError::Cr2(_))));
261    }
262
263    #[test]
264    fn test_encode_error_conversion() {
265        let enc_err = EncodeError::Encoding {
266            format: "PNG",
267            message: "test".to_string(),
268        };
269        let raw_err: RawError = enc_err.into();
270        assert!(matches!(
271            raw_err,
272            RawError::Encode(EncodeError::Encoding { .. })
273        ));
274    }
275}