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