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 | |
16 | use std::error::Error; |
17 | use std::{fmt, io}; |
18 | |
19 | use crate::color::ExtendedColorType; |
20 | use 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)] |
27 | pub 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)] |
73 | pub 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 ] |
81 | pub 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)] |
98 | pub 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)] |
110 | pub 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 ] |
118 | pub 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)] |
137 | pub 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)] |
149 | pub 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. |
161 | pub 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 ] |
178 | pub 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 | |
192 | impl 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 | |
215 | impl 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 | |
242 | impl 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 | |
269 | impl 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 | |
286 | impl 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 | |
300 | impl From<io::Error> for ImageError { |
301 | fn from(err: io::Error) -> ImageError { |
302 | ImageError::IoError(err) |
303 | } |
304 | } |
305 | |
306 | impl From<ImageFormat> for ImageFormatHint { |
307 | fn from(format: ImageFormat) -> Self { |
308 | ImageFormatHint::Exact(format) |
309 | } |
310 | } |
311 | |
312 | impl From<&'_ std::path::Path> for ImageFormatHint { |
313 | fn from(path: &'_ std::path::Path) -> Self { |
314 | match path.extension() { |
315 | Some(ext: &OsStr) => ImageFormatHint::PathExtension(ext.into()), |
316 | None => ImageFormatHint::Unknown, |
317 | } |
318 | } |
319 | } |
320 | |
321 | impl 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 |
331 | pub type ImageResult<T> = Result<T, ImageError>; |
332 | |
333 | impl fmt::Display for ImageError { |
334 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
335 | match self { |
336 | ImageError::IoError(err: &Error) => err.fmt(fmt), |
337 | ImageError::Decoding(err: &DecodingError) => err.fmt(fmt), |
338 | ImageError::Encoding(err: &EncodingError) => err.fmt(fmt), |
339 | ImageError::Parameter(err: &ParameterError) => err.fmt(fmt), |
340 | ImageError::Limits(err: &LimitError) => err.fmt(fmt), |
341 | ImageError::Unsupported(err: &UnsupportedError) => err.fmt(fmt), |
342 | } |
343 | } |
344 | } |
345 | |
346 | impl Error for ImageError { |
347 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
348 | match self { |
349 | ImageError::IoError(err: &Error) => err.source(), |
350 | ImageError::Decoding(err: &DecodingError) => err.source(), |
351 | ImageError::Encoding(err: &EncodingError) => err.source(), |
352 | ImageError::Parameter(err: &ParameterError) => err.source(), |
353 | ImageError::Limits(err: &LimitError) => err.source(), |
354 | ImageError::Unsupported(err: &UnsupportedError) => err.source(), |
355 | } |
356 | } |
357 | } |
358 | |
359 | impl 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 | |
391 | impl Error for UnsupportedError {} |
392 | |
393 | impl 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 | |
419 | impl Error for ParameterError { |
420 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
421 | match &self.underlying { |
422 | None => None, |
423 | Some(source: &Box) => Some(&**source), |
424 | } |
425 | } |
426 | } |
427 | |
428 | impl fmt::Display for EncodingError { |
429 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
430 | match &self.underlying { |
431 | Some(underlying: &Box) => 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 | |
441 | impl Error for EncodingError { |
442 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
443 | match &self.underlying { |
444 | None => None, |
445 | Some(source: &Box) => Some(&**source), |
446 | } |
447 | } |
448 | } |
449 | |
450 | impl 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: &Box) => { |
458 | write!(fmt, "Format error decoding {}: {}" , self.format, underlying) |
459 | } |
460 | } |
461 | } |
462 | } |
463 | |
464 | impl Error for DecodingError { |
465 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
466 | match &self.underlying { |
467 | None => None, |
468 | Some(source: &Box) => Some(&**source), |
469 | } |
470 | } |
471 | } |
472 | |
473 | impl 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 | |
486 | impl Error for LimitError {} |
487 | |
488 | impl fmt::Display for ImageFormatHint { |
489 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
490 | match self { |
491 | ImageFormatHint::Exact(format: &ImageFormat) => write!(fmt, " {format:?}" ), |
492 | ImageFormatHint::Name(name: &String) => write!(fmt, "` {name}`" ), |
493 | ImageFormatHint::PathExtension(ext: &PathBuf) => write!(fmt, "`. {ext:?}`" ), |
494 | ImageFormatHint::Unknown => write!(fmt, "`Unknown`" ), |
495 | } |
496 | } |
497 | } |
498 | |
499 | #[cfg (test)] |
500 | mod 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 | } |
515 | |