image/
error.rs

1//! Contains detailed error representation.
2//!
3//! See the main [`ImageError`] which contains a variant for each specialized error type. The
4//! subtypes used in each variant are opaque by design. They can be roughly inspected through their
5//! respective `kind` methods which work similar to `std::io::Error::kind`.
6//!
7//! The error interface makes it possible to inspect the error of an underlying decoder or encoder,
8//! through the `Error::source` method. Note that this is not part of the stable interface and you
9//! may not rely on a particular error value for a particular operation. This means mainly that
10//! `image` does not promise to remain on a particular version of its underlying decoders but if
11//! you ensure to use the same version of the dependency (or at least of the error type) through
12//! external means then you could inspect the error type in slightly more detail.
13//!
14//! [`ImageError`]: enum.ImageError.html
15
16use std::error::Error;
17use std::{fmt, io};
18
19use crate::color::ExtendedColorType;
20use crate::image::ImageFormat;
21
22/// The generic error type for image operations.
23///
24/// This high level enum allows, by variant matching, a rough separation of concerns between
25/// underlying IO, the caller, format specifications, and the `image` implementation.
26#[derive(Debug)]
27pub enum ImageError {
28    /// An error was encountered while decoding.
29    ///
30    /// This means that the input data did not conform to the specification of some image format,
31    /// or that no format could be determined, or that it did not match format specific
32    /// requirements set by the caller.
33    Decoding(DecodingError),
34
35    /// An error was encountered while encoding.
36    ///
37    /// The input image can not be encoded with the chosen format, for example because the
38    /// specification has no representation for its color space or because a necessary conversion
39    /// is ambiguous. In some cases it might also happen that the dimensions can not be used with
40    /// the format.
41    Encoding(EncodingError),
42
43    /// An error was encountered in input arguments.
44    ///
45    /// This is a catch-all case for strictly internal operations such as scaling, conversions,
46    /// etc. that involve no external format specifications.
47    Parameter(ParameterError),
48
49    /// Completing the operation would have required more resources than allowed.
50    ///
51    /// Errors of this type are limits set by the user or environment, *not* inherent in a specific
52    /// format or operation that was executed.
53    Limits(LimitError),
54
55    /// An operation can not be completed by the chosen abstraction.
56    ///
57    /// This means that it might be possible for the operation to succeed in general but
58    /// * it requires a disabled feature,
59    /// * the implementation does not yet exist, or
60    /// * no abstraction for a lower level could be found.
61    Unsupported(UnsupportedError),
62
63    /// An error occurred while interacting with the environment.
64    IoError(io::Error),
65}
66
67/// The implementation for an operation was not provided.
68///
69/// See the variant [`Unsupported`] for more documentation.
70///
71/// [`Unsupported`]: enum.ImageError.html#variant.Unsupported
72#[derive(Debug)]
73pub struct UnsupportedError {
74    format: ImageFormatHint,
75    kind: UnsupportedErrorKind,
76}
77
78/// Details what feature is not supported.
79#[derive(Clone, Debug, Hash, PartialEq)]
80#[non_exhaustive]
81pub enum UnsupportedErrorKind {
82    /// The required color type can not be handled.
83    Color(ExtendedColorType),
84    /// An image format is not supported.
85    Format(ImageFormatHint),
86    /// Some feature specified by string.
87    /// This is discouraged and is likely to get deprecated (but not removed).
88    GenericFeature(String),
89}
90
91/// An error was encountered while encoding an image.
92///
93/// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its
94/// documentation for more information.
95///
96/// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding
97#[derive(Debug)]
98pub struct EncodingError {
99    format: ImageFormatHint,
100    underlying: Option<Box<dyn Error + Send + Sync>>,
101}
102
103/// An error was encountered in inputs arguments.
104///
105/// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its
106/// documentation for more information.
107///
108/// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter
109#[derive(Debug)]
110pub struct ParameterError {
111    kind: ParameterErrorKind,
112    underlying: Option<Box<dyn Error + Send + Sync>>,
113}
114
115/// Details how a parameter is malformed.
116#[derive(Clone, Debug, Hash, PartialEq)]
117#[non_exhaustive]
118pub enum ParameterErrorKind {
119    /// The dimensions passed are wrong.
120    DimensionMismatch,
121    /// Repeated an operation for which error that could not be cloned was emitted already.
122    FailedAlready,
123    /// A string describing the parameter.
124    /// This is discouraged and is likely to get deprecated (but not removed).
125    Generic(String),
126    /// The end of the image has been reached.
127    NoMoreData,
128}
129
130/// An error was encountered while decoding an image.
131///
132/// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its
133/// documentation for more information.
134///
135/// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding
136#[derive(Debug)]
137pub struct DecodingError {
138    format: ImageFormatHint,
139    underlying: Option<Box<dyn Error + Send + Sync>>,
140}
141
142/// Completing the operation would have required more resources than allowed.
143///
144/// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its
145/// documentation for more information.
146///
147/// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits
148#[derive(Debug)]
149pub struct LimitError {
150    kind: LimitErrorKind,
151    // do we need an underlying error?
152}
153
154/// Indicates the limit that prevented an operation from completing.
155///
156/// Note that this enumeration is not exhaustive and may in the future be extended to provide more
157/// detailed information or to incorporate other resources types.
158#[derive(Clone, Debug, Hash, PartialEq, Eq)]
159#[non_exhaustive]
160#[allow(missing_copy_implementations)] // Might be non-Copy in the future.
161pub enum LimitErrorKind {
162    /// The resulting image exceed dimension limits in either direction.
163    DimensionError,
164    /// The operation would have performed an allocation larger than allowed.
165    InsufficientMemory,
166    /// The specified strict limits are not supported for this operation
167    Unsupported {
168        /// The given limits
169        limits: crate::Limits,
170        /// The supported strict limits
171        supported: crate::LimitSupport,
172    },
173}
174
175/// A best effort representation for image formats.
176#[derive(Clone, Debug, Hash, PartialEq)]
177#[non_exhaustive]
178pub enum ImageFormatHint {
179    /// The format is known exactly.
180    Exact(ImageFormat),
181
182    /// The format can be identified by a name.
183    Name(String),
184
185    /// A common path extension for the format is known.
186    PathExtension(std::path::PathBuf),
187
188    /// The format is not known or could not be determined.
189    Unknown,
190}
191
192impl UnsupportedError {
193    /// Create an `UnsupportedError` for an image with details on the unsupported feature.
194    ///
195    /// If the operation was not connected to a particular image format then the hint may be
196    /// `Unknown`.
197    #[must_use]
198    pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self {
199        UnsupportedError { format, kind }
200    }
201
202    /// Returns the corresponding `UnsupportedErrorKind` of the error.
203    #[must_use]
204    pub fn kind(&self) -> UnsupportedErrorKind {
205        self.kind.clone()
206    }
207
208    /// Returns the image format associated with this error.
209    #[must_use]
210    pub fn format_hint(&self) -> ImageFormatHint {
211        self.format.clone()
212    }
213}
214
215impl DecodingError {
216    /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder.
217    pub fn new(format: ImageFormatHint, err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
218        DecodingError {
219            format,
220            underlying: Some(err.into()),
221        }
222    }
223
224    /// Create a `DecodingError` for an image format.
225    ///
226    /// The error will not contain any further information but is very easy to create.
227    #[must_use]
228    pub fn from_format_hint(format: ImageFormatHint) -> Self {
229        DecodingError {
230            format,
231            underlying: None,
232        }
233    }
234
235    /// Returns the image format associated with this error.
236    #[must_use]
237    pub fn format_hint(&self) -> ImageFormatHint {
238        self.format.clone()
239    }
240}
241
242impl EncodingError {
243    /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder.
244    pub fn new(format: ImageFormatHint, err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
245        EncodingError {
246            format,
247            underlying: Some(err.into()),
248        }
249    }
250
251    /// Create an `EncodingError` for an image format.
252    ///
253    /// The error will not contain any further information but is very easy to create.
254    #[must_use]
255    pub fn from_format_hint(format: ImageFormatHint) -> Self {
256        EncodingError {
257            format,
258            underlying: None,
259        }
260    }
261
262    /// Return the image format associated with this error.
263    #[must_use]
264    pub fn format_hint(&self) -> ImageFormatHint {
265        self.format.clone()
266    }
267}
268
269impl ParameterError {
270    /// Construct a `ParameterError` directly from a corresponding kind.
271    #[must_use]
272    pub fn from_kind(kind: ParameterErrorKind) -> Self {
273        ParameterError {
274            kind,
275            underlying: None,
276        }
277    }
278
279    /// Returns the corresponding `ParameterErrorKind` of the error.
280    #[must_use]
281    pub fn kind(&self) -> ParameterErrorKind {
282        self.kind.clone()
283    }
284}
285
286impl LimitError {
287    /// Construct a generic `LimitError` directly from a corresponding kind.
288    #[must_use]
289    pub fn from_kind(kind: LimitErrorKind) -> Self {
290        LimitError { kind }
291    }
292
293    /// Returns the corresponding `LimitErrorKind` of the error.
294    #[must_use]
295    pub fn kind(&self) -> LimitErrorKind {
296        self.kind.clone()
297    }
298}
299
300impl From<io::Error> for ImageError {
301    fn from(err: io::Error) -> ImageError {
302        ImageError::IoError(err)
303    }
304}
305
306impl From<ImageFormat> for ImageFormatHint {
307    fn from(format: ImageFormat) -> Self {
308        ImageFormatHint::Exact(format)
309    }
310}
311
312impl From<&'_ std::path::Path> for ImageFormatHint {
313    fn from(path: &'_ std::path::Path) -> Self {
314        match path.extension() {
315            Some(ext) => ImageFormatHint::PathExtension(ext.into()),
316            None => ImageFormatHint::Unknown,
317        }
318    }
319}
320
321impl From<ImageFormatHint> for UnsupportedError {
322    fn from(hint: ImageFormatHint) -> Self {
323        UnsupportedError {
324            format: hint.clone(),
325            kind: UnsupportedErrorKind::Format(hint),
326        }
327    }
328}
329
330/// Result of an image decoding/encoding process
331pub type ImageResult<T> = Result<T, ImageError>;
332
333impl fmt::Display for ImageError {
334    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
335        match self {
336            ImageError::IoError(err) => err.fmt(fmt),
337            ImageError::Decoding(err) => err.fmt(fmt),
338            ImageError::Encoding(err) => err.fmt(fmt),
339            ImageError::Parameter(err) => err.fmt(fmt),
340            ImageError::Limits(err) => err.fmt(fmt),
341            ImageError::Unsupported(err) => err.fmt(fmt),
342        }
343    }
344}
345
346impl Error for ImageError {
347    fn source(&self) -> Option<&(dyn Error + 'static)> {
348        match self {
349            ImageError::IoError(err) => err.source(),
350            ImageError::Decoding(err) => err.source(),
351            ImageError::Encoding(err) => err.source(),
352            ImageError::Parameter(err) => err.source(),
353            ImageError::Limits(err) => err.source(),
354            ImageError::Unsupported(err) => err.source(),
355        }
356    }
357}
358
359impl fmt::Display for UnsupportedError {
360    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
361        match &self.kind {
362            UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => {
363                write!(fmt, "The image format could not be determined",)
364            }
365            UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!(
366                fmt,
367                "The file extension {format} was not recognized as an image format",
368            ),
369            UnsupportedErrorKind::Format(format) => {
370                write!(fmt, "The image format {format} is not supported",)
371            }
372            UnsupportedErrorKind::Color(color) => write!(
373                fmt,
374                "The encoder or decoder for {} does not support the color type `{:?}`",
375                self.format, color,
376            ),
377            UnsupportedErrorKind::GenericFeature(message) => match &self.format {
378                ImageFormatHint::Unknown => write!(
379                    fmt,
380                    "The decoder does not support the format feature {message}",
381                ),
382                other => write!(
383                    fmt,
384                    "The decoder for {other} does not support the format features {message}",
385                ),
386            },
387        }
388    }
389}
390
391impl Error for UnsupportedError {}
392
393impl fmt::Display for ParameterError {
394    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
395        match &self.kind {
396            ParameterErrorKind::DimensionMismatch => write!(
397                fmt,
398                "The Image's dimensions are either too \
399                 small or too large"
400            ),
401            ParameterErrorKind::FailedAlready => write!(
402                fmt,
403                "The end the image stream has been reached due to a previous error"
404            ),
405            ParameterErrorKind::Generic(message) => {
406                write!(fmt, "The parameter is malformed: {message}",)
407            }
408            ParameterErrorKind::NoMoreData => write!(fmt, "The end of the image has been reached",),
409        }?;
410
411        if let Some(underlying) = &self.underlying {
412            write!(fmt, "\n{underlying}")?;
413        }
414
415        Ok(())
416    }
417}
418
419impl Error for ParameterError {
420    fn source(&self) -> Option<&(dyn Error + 'static)> {
421        match &self.underlying {
422            None => None,
423            Some(source) => Some(&**source),
424        }
425    }
426}
427
428impl fmt::Display for EncodingError {
429    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
430        match &self.underlying {
431            Some(underlying) => write!(
432                fmt,
433                "Format error encoding {}:\n{}",
434                self.format, underlying,
435            ),
436            None => write!(fmt, "Format error encoding {}", self.format,),
437        }
438    }
439}
440
441impl Error for EncodingError {
442    fn source(&self) -> Option<&(dyn Error + 'static)> {
443        match &self.underlying {
444            None => None,
445            Some(source) => Some(&**source),
446        }
447    }
448}
449
450impl fmt::Display for DecodingError {
451    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
452        match &self.underlying {
453            None => match self.format {
454                ImageFormatHint::Unknown => write!(fmt, "Format error"),
455                _ => write!(fmt, "Format error decoding {}", self.format),
456            },
457            Some(underlying) => {
458                write!(fmt, "Format error decoding {}: {}", self.format, underlying)
459            }
460        }
461    }
462}
463
464impl Error for DecodingError {
465    fn source(&self) -> Option<&(dyn Error + 'static)> {
466        match &self.underlying {
467            None => None,
468            Some(source) => Some(&**source),
469        }
470    }
471}
472
473impl fmt::Display for LimitError {
474    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
475        match self.kind {
476            LimitErrorKind::InsufficientMemory => write!(fmt, "Memory limit exceeded"),
477            LimitErrorKind::DimensionError => write!(fmt, "Image size exceeds limit"),
478            LimitErrorKind::Unsupported { .. } => {
479                write!(fmt, "The following strict limits are specified but not supported by the opertation: ")?;
480                Ok(())
481            }
482        }
483    }
484}
485
486impl Error for LimitError {}
487
488impl fmt::Display for ImageFormatHint {
489    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
490        match self {
491            ImageFormatHint::Exact(format) => write!(fmt, "{format:?}"),
492            ImageFormatHint::Name(name) => write!(fmt, "`{name}`"),
493            ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{ext:?}`"),
494            ImageFormatHint::Unknown => write!(fmt, "`Unknown`"),
495        }
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502    use std::mem::size_of;
503
504    #[allow(dead_code)]
505    // This will fail to compile if the size of this type is large.
506    const ASSERT_SMALLISH: usize = [0][(size_of::<ImageError>() >= 200) as usize];
507
508    #[test]
509    fn test_send_sync_stability() {
510        fn assert_send_sync<T: Send + Sync>() {}
511
512        assert_send_sync::<ImageError>();
513    }
514}