Skip to main content

async_tiff/
error.rs

1//! Error handling.
2
3use std::error::Error;
4use std::fmt::Debug;
5use std::{fmt, io, str, string};
6
7use jpeg::UnsupportedFeature;
8use thiserror::Error;
9
10use crate::tag_value::TagValue;
11use crate::tags::{
12    CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
13};
14
15/// Enum with all errors in this crate.
16#[derive(Error, Debug)]
17#[non_exhaustive]
18pub enum AsyncTiffError {
19    /// End of file error.
20    #[error("End of File: expected to read {0} bytes, got {1}")]
21    EndOfFile(u64, u64),
22
23    /// General error.
24    #[error("General error: {0}")]
25    General(String),
26
27    /// Tile index error
28    #[error("Tile index out of bounds: {0}, {1}")]
29    TileIndexError(u32, u32),
30
31    /// IO Error.
32    #[error(transparent)]
33    IOError(#[from] std::io::Error),
34
35    /// Error while decoding JPEG data.
36    #[error(transparent)]
37    JPEGDecodingError(#[from] jpeg::Error),
38
39    /// Error while decoding JPEG2000 data.
40    #[cfg(feature = "jpeg2k")]
41    #[error(transparent)]
42    JPEG2kDecodingError(#[from] jpeg2k::error::Error),
43
44    /// Error while fetching data using object store.
45    #[cfg(feature = "object_store")]
46    #[error(transparent)]
47    ObjectStore(#[from] object_store::Error),
48
49    /// An error during TIFF tag parsing.
50    #[error(transparent)]
51    InternalTIFFError(#[from] TiffError),
52
53    /// Reqwest error
54    #[cfg(feature = "reqwest")]
55    #[error(transparent)]
56    ReqwestError(#[from] reqwest::Error),
57
58    /// External error
59    #[error(transparent)]
60    External(Box<dyn std::error::Error + Send + Sync>),
61}
62
63/// Crate-specific result type.
64pub type AsyncTiffResult<T> = std::result::Result<T, AsyncTiffError>;
65
66/// Tiff error kinds.
67#[derive(Debug)]
68#[allow(clippy::enum_variant_names)]
69pub enum TiffError {
70    /// The Image is not formatted properly.
71    FormatError(TiffFormatError),
72
73    /// The Decoder does not support features required by the image.
74    UnsupportedError(TiffUnsupportedError),
75
76    /// An I/O Error occurred while decoding the image.
77    IoError(io::Error),
78
79    /// An integer conversion to or from a platform size failed, either due to
80    /// limits of the platform size or limits of the format.
81    IntSizeError,
82
83    /// The image does not support the requested operation
84    UsageError(UsageError),
85}
86
87/// The image is not formatted properly.
88///
89/// This indicates that the encoder producing the image might behave incorrectly or that the input
90/// file has been corrupted.
91///
92/// The list of variants may grow to incorporate errors of future features. Matching against this
93/// exhaustively is not covered by interface stability guarantees.
94#[derive(Debug, Clone, PartialEq)]
95#[non_exhaustive]
96#[expect(missing_docs)]
97pub enum TiffFormatError {
98    TiffSignatureNotFound,
99    TiffSignatureInvalid,
100    ImageFileDirectoryNotFound,
101    InconsistentSizesEncountered,
102    UnexpectedCompressedData {
103        actual_bytes: usize,
104        required_bytes: usize,
105    },
106    InconsistentStripSamples {
107        actual_samples: usize,
108        required_samples: usize,
109    },
110    InvalidDimensions(u32, u32),
111    InvalidTag,
112    InvalidTagValueType(Tag),
113    RequiredTagNotFound(Tag),
114    UnknownPredictor(u16),
115    UnknownPlanarConfiguration(u16),
116    ByteExpected(TagValue),
117    SignedByteExpected(TagValue),
118    ShortExpected(TagValue),
119    SignedShortExpected(TagValue),
120    UnsignedIntegerExpected(TagValue),
121    SignedIntegerExpected(TagValue),
122    Format(String),
123    RequiredTagEmpty(Tag),
124    StripTileTagConflict,
125    CycleInOffsets,
126    SamplesPerPixelIsZero,
127}
128
129impl fmt::Display for TiffFormatError {
130    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
131        use self::TiffFormatError::*;
132        match *self {
133            TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
134            TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
135            ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
136            InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
137            UnexpectedCompressedData {
138                actual_bytes,
139                required_bytes,
140            } => {
141                write!(
142                    fmt,
143                    "Decompression returned different amount of bytes than expected: got {actual_bytes}, expected {required_bytes}."
144                )
145            }
146            InconsistentStripSamples {
147                actual_samples,
148                required_samples,
149            } => {
150                write!(
151                    fmt,
152                    "Inconsistent elements in strip: got {actual_samples}, expected {required_samples}."
153                )
154            }
155            InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {width}x{height}."),
156            InvalidTag => write!(fmt, "Image contains invalid tag."),
157            InvalidTagValueType(ref tag) => {
158                write!(fmt, "Tag `{tag:?}` did not have the expected value type.")
159            }
160            RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{tag:?}` not found."),
161            UnknownPredictor(ref predictor) => {
162                write!(fmt, "Unknown predictor “{predictor}” encountered")
163            }
164            UnknownPlanarConfiguration(ref planar_config) =>  {
165                write!(fmt, "Unknown planar configuration “{planar_config}” encountered")
166            }
167            ByteExpected(ref val) => write!(fmt, "Expected byte, {val:?} found."),
168            SignedByteExpected(ref val) => write!(fmt, "Expected signed byte, {val:?} found."),
169            ShortExpected(ref val) => write!(fmt, "Expected short, {val:?} found."),
170            SignedShortExpected(ref val) => write!(fmt, "Expected signed short, {val:?} found."),
171            UnsignedIntegerExpected(ref val) => {
172                write!(fmt, "Expected unsigned integer, {val:?} found.")
173            }
174            SignedIntegerExpected(ref val) => {
175                write!(fmt, "Expected signed integer, {val:?} found.")
176            }
177            Format(ref val) => write!(fmt, "Invalid format: {val:?}."),
178            RequiredTagEmpty(ref val) => write!(fmt, "Required tag {val:?} was empty."),
179            StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
180            CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
181            SamplesPerPixelIsZero => write!(fmt, "Samples per pixel is zero"),
182        }
183    }
184}
185
186/// The Decoder does not support features required by the image.
187///
188/// This only captures known failures for which the standard either does not require support or an
189/// implementation has been planned but not yet completed. Some variants may become unused over
190/// time and will then get deprecated before being removed.
191///
192/// The list of variants may grow. Matching against this exhaustively is not covered by interface
193/// stability guarantees.
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195#[expect(missing_docs)]
196#[non_exhaustive]
197pub enum TiffUnsupportedError {
198    // FloatingPointPredictor(ColorType),
199    // HorizontalPredictor(ColorType),
200    InconsistentBitsPerSample(Vec<u8>),
201    InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
202    UnknownInterpretation,
203    UnknownCompressionMethod,
204    UnsupportedCompressionMethod(CompressionMethod),
205    UnsupportedPredictor(Predictor),
206    UnsupportedSampleDepth(u8),
207    UnsupportedSampleFormat(Vec<SampleFormat>),
208    // UnsupportedColorType(ColorType),
209    UnsupportedBitsPerChannel(u8),
210    UnsupportedPlanarConfig(Option<PlanarConfiguration>),
211    UnsupportedDataType,
212    UnsupportedInterpretation(PhotometricInterpretation),
213    UnsupportedJpegFeature(UnsupportedFeature),
214    MisalignedTileBoundaries,
215}
216
217impl fmt::Display for TiffUnsupportedError {
218    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
219        use self::TiffUnsupportedError::*;
220        match *self {
221            // FloatingPointPredictor(color_type) => write!(
222            //     fmt,
223            //     "Floating point predictor for {:?} is unsupported.",
224            //     color_type
225            // ),
226            // HorizontalPredictor(color_type) => write!(
227            //     fmt,
228            //     "Horizontal predictor for {:?} is unsupported.",
229            //     color_type
230            // ),
231            InconsistentBitsPerSample(ref bits_per_sample) => {
232                write!(fmt, "Inconsistent bits per sample: {bits_per_sample:?}.")
233            }
234            InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
235                fmt,
236                "{photometric_interpretation:?} with {bits_per_sample:?} bits per sample is unsupported"
237            ),
238            UnknownInterpretation => write!(
239                fmt,
240                "The image is using an unknown photometric interpretation."
241            ),
242            UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
243            UnsupportedCompressionMethod(method) => {
244                write!(fmt, "Compression method {method:?} is unsupported")
245            }
246            UnsupportedPredictor(p) => {
247                write!(fmt, "Predictor {p:?} is unsupported")
248            }
249            UnsupportedSampleDepth(samples) => {
250                write!(fmt, "{samples} samples per pixel is unsupported.")
251            }
252            UnsupportedSampleFormat(ref formats) => {
253                write!(fmt, "Sample format {formats:?} is unsupported.")
254            }
255            // UnsupportedColorType(color_type) => {
256            //     write!(fmt, "Color type {:?} is unsupported", color_type)
257            // }
258            UnsupportedBitsPerChannel(bits) => {
259                write!(fmt, "{bits} bits per channel not supported")
260            }
261            UnsupportedPlanarConfig(config) => {
262                write!(fmt, "Unsupported planar configuration “{config:?}”.")
263            }
264            UnsupportedDataType => write!(fmt, "Unsupported data type."),
265            UnsupportedInterpretation(interpretation) => {
266                write!(
267                    fmt,
268                    "Unsupported photometric interpretation \"{interpretation:?}\"."
269                )
270            }
271            UnsupportedJpegFeature(ref unsupported_feature) => {
272                write!(fmt, "Unsupported JPEG feature {unsupported_feature:?}")
273            }
274            MisalignedTileBoundaries => write!(fmt, "Tile rows are not aligned to byte boundaries"),
275        }
276    }
277}
278
279/// User attempted to use the Decoder in a way that is incompatible with a specific image.
280///
281/// For example: attempting to read a tile from a stripped image.
282#[expect(missing_docs)]
283#[derive(Debug)]
284pub enum UsageError {
285    // InvalidChunkType(ChunkType, ChunkType),
286    InvalidChunkIndex(u32),
287    PredictorCompressionMismatch,
288    PredictorIncompatible,
289    PredictorUnavailable,
290}
291
292impl fmt::Display for UsageError {
293    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
294        use self::UsageError::*;
295        match *self {
296            // InvalidChunkType(expected, actual) => {
297            //     write!(
298            //         fmt,
299            //         "Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
300            //         expected, actual
301            //     )
302            // }
303            InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({index}) requested."),
304            PredictorCompressionMismatch => write!(
305                fmt,
306                "The requested predictor is not compatible with the requested compression"
307            ),
308            PredictorIncompatible => write!(
309                fmt,
310                "The requested predictor is not compatible with the image's format"
311            ),
312            PredictorUnavailable => write!(fmt, "The requested predictor is not available"),
313        }
314    }
315}
316
317impl fmt::Display for TiffError {
318    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
319        match *self {
320            TiffError::FormatError(ref e) => write!(fmt, "Format error: {e}"),
321            TiffError::UnsupportedError(ref f) => write!(
322                fmt,
323                "The Decoder does not support the \
324                 image format `{f}`"
325            ),
326            TiffError::IoError(ref e) => write!(fmt, "{}", e),
327            TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
328            TiffError::UsageError(ref e) => write!(fmt, "Usage error: {e}"),
329        }
330    }
331}
332
333impl Error for TiffError {
334    fn description(&self) -> &str {
335        match *self {
336            TiffError::FormatError(..) => "Format error",
337            TiffError::UnsupportedError(..) => "Unsupported error",
338            TiffError::IoError(..) => "IO error",
339            TiffError::IntSizeError => "Platform or format size limits exceeded",
340            TiffError::UsageError(..) => "Invalid usage",
341        }
342    }
343
344    fn cause(&self) -> Option<&dyn Error> {
345        match *self {
346            TiffError::IoError(ref e) => Some(e),
347            _ => None,
348        }
349    }
350}
351
352impl From<io::Error> for TiffError {
353    fn from(err: io::Error) -> TiffError {
354        TiffError::IoError(err)
355    }
356}
357
358impl From<str::Utf8Error> for TiffError {
359    fn from(_err: str::Utf8Error) -> TiffError {
360        TiffError::FormatError(TiffFormatError::InvalidTag)
361    }
362}
363
364impl From<string::FromUtf8Error> for TiffError {
365    fn from(_err: string::FromUtf8Error) -> TiffError {
366        TiffError::FormatError(TiffFormatError::InvalidTag)
367    }
368}
369
370impl From<TiffFormatError> for TiffError {
371    fn from(err: TiffFormatError) -> TiffError {
372        TiffError::FormatError(err)
373    }
374}
375
376impl From<TiffUnsupportedError> for TiffError {
377    fn from(err: TiffUnsupportedError) -> TiffError {
378        TiffError::UnsupportedError(err)
379    }
380}
381
382impl From<UsageError> for TiffError {
383    fn from(err: UsageError) -> TiffError {
384        TiffError::UsageError(err)
385    }
386}
387
388impl From<std::num::TryFromIntError> for TiffError {
389    fn from(_err: std::num::TryFromIntError) -> TiffError {
390        TiffError::IntSizeError
391    }
392}
393
394// impl From<LzwError> for TiffError {
395//     fn from(err: LzwError) -> TiffError {
396//         match err {
397//             LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
398//                 "LZW compressed data corrupted",
399//             ))),
400//         }
401//     }
402// }
403
404/// Result of an image decoding/encoding process
405pub type TiffResult<T> = Result<T, TiffError>;