1use std::fmt::{Debug, Display};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum Error {
12 #[error("io error: {0}")]
13 Io(#[from] std::io::Error),
14
15 #[error("unsupported media format")]
16 UnsupportedFormat,
17
18 #[error("no exif data found in this file")]
19 ExifNotFound,
20
21 #[error("no track info found in this file")]
22 TrackNotFound,
23
24 #[error("malformed {kind}: {message}")]
26 Malformed {
27 kind: MalformedKind,
28 message: String,
29 },
30
31 #[error("unexpected end of input while parsing {context}")]
33 UnexpectedEof { context: &'static str },
34}
35
36#[derive(Debug, Error)]
37pub(crate) enum ParsedError {
38 #[error("no enough bytes")]
39 NoEnoughBytes,
40
41 #[error("io error: {0}")]
42 IOError(std::io::Error),
43
44 #[error("{0}")]
45 Failed(String),
46}
47
48#[derive(Debug, Error)]
86pub(crate) enum ParsingError {
87 #[error("need more bytes: {0}")]
88 Need(usize),
89
90 #[error("clear and skip bytes: {0:?}")]
91 ClearAndSkip(usize),
92
93 #[error("{0}")]
94 Failed(String),
95}
96
97#[derive(Debug, Error)]
98pub(crate) struct ParsingErrorState {
99 pub err: ParsingError,
100 pub state: Option<ParsingState>,
101}
102
103impl ParsingErrorState {
104 pub fn new(err: ParsingError, state: Option<ParsingState>) -> Self {
105 Self { err, state }
106 }
107}
108
109impl Display for ParsingErrorState {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 Display::fmt(
112 &format!(
113 "ParsingError(err: {}, state: {})",
114 self.err,
115 self.state
116 .as_ref()
117 .map(|x| x.to_string())
118 .unwrap_or("None".to_string())
119 ),
120 f,
121 )
122 }
123}
124
125impl From<&str> for ParsingError {
126 fn from(value: &str) -> Self {
127 Self::Failed(value.to_string())
128 }
129}
130
131impl From<std::io::Error> for ParsedError {
132 fn from(value: std::io::Error) -> Self {
133 Self::IOError(value)
134 }
135}
136
137impl From<ParsedError> for crate::Error {
138 fn from(value: ParsedError) -> Self {
139 match value {
140 ParsedError::NoEnoughBytes => Self::UnexpectedEof {
141 context: "media stream",
142 },
143 ParsedError::IOError(e) => Self::Io(e),
144 ParsedError::Failed(e) => Self::Malformed {
147 kind: MalformedKind::IsoBmffBox,
148 message: e,
149 },
150 }
151 }
152}
153
154use crate::parser::ParsingState;
155
156impl<T: Debug> From<nom::Err<nom::error::Error<T>>> for crate::Error {
157 fn from(e: nom::Err<nom::error::Error<T>>) -> Self {
158 convert_parse_error(e, "")
159 }
160}
161
162pub(crate) fn convert_parse_error<T: Debug>(
163 e: nom::Err<nom::error::Error<T>>,
164 message: &str,
165) -> Error {
166 let s = match e {
167 nom::Err::Incomplete(_) => format!("{e}; {message}"),
168 nom::Err::Error(e) => format!("{}; {message}", e.code.description()),
169 nom::Err::Failure(e) => format!("{}; {message}", e.code.description()),
170 };
171 Error::Malformed {
172 kind: MalformedKind::TiffHeader,
173 message: s,
174 }
175}
176
177impl From<nom::Err<nom::error::Error<&[u8]>>> for ParsingError {
178 fn from(e: nom::Err<nom::error::Error<&[u8]>>) -> Self {
179 match e {
180 nom::Err::Incomplete(needed) => match needed {
181 nom::Needed::Unknown => ParsingError::Need(1),
182 nom::Needed::Size(n) => ParsingError::Need(n.get()),
183 },
184 nom::Err::Failure(e) | nom::Err::Error(e) => {
185 ParsingError::Failed(e.code.description().to_string())
186 }
187 }
188 }
189}
190
191pub(crate) fn nom_error_to_parsing_error_with_state(
192 e: nom::Err<nom::error::Error<&[u8]>>,
193 state: Option<ParsingState>,
194) -> ParsingErrorState {
195 match e {
196 nom::Err::Incomplete(needed) => match needed {
197 nom::Needed::Unknown => ParsingErrorState::new(ParsingError::Need(1), state),
198 nom::Needed::Size(n) => ParsingErrorState::new(ParsingError::Need(n.get()), state),
199 },
200 nom::Err::Failure(e) | nom::Err::Error(e) => ParsingErrorState::new(
201 ParsingError::Failed(e.code.description().to_string()),
202 state,
203 ),
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219#[non_exhaustive]
220pub enum MalformedKind {
221 JpegSegment,
222 TiffHeader,
223 IfdEntry,
224 IsoBmffBox,
225 EbmlElement,
226}
227
228impl std::fmt::Display for MalformedKind {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 let s = match self {
231 Self::JpegSegment => "jpeg segment",
232 Self::TiffHeader => "tiff header",
233 Self::IfdEntry => "ifd entry",
234 Self::IsoBmffBox => "iso-bmff box",
235 Self::EbmlElement => "ebml element",
236 };
237 f.write_str(s)
238 }
239}
240
241#[derive(Debug, Clone, thiserror::Error)]
250#[non_exhaustive]
251pub enum ConvertError {
252 #[error("unknown ExifTag name: {0}")]
253 UnknownTagName(String),
254
255 #[error("invalid ISO 6709 coordinate: {0}")]
256 InvalidIso6709(String),
257
258 #[error("rational has negative value")]
259 NegativeRational,
260
261 #[error("decimal degrees out of range or non-finite: {0}")]
262 InvalidDecimalDegrees(f64),
263}
264
265#[derive(Debug, Clone, PartialEq, thiserror::Error)]
272#[non_exhaustive]
273pub enum EntryError {
274 #[error("entry truncated: needed {needed} bytes, only {available} available")]
275 Truncated { needed: usize, available: usize },
276
277 #[error("invalid entry shape: format={format}, count={count}")]
278 InvalidShape { format: u16, count: u32 },
279
280 #[error("invalid value: {0}")]
281 InvalidValue(&'static str),
282}
283
284impl From<EntryError> for Error {
285 fn from(e: EntryError) -> Self {
286 Error::Malformed {
287 kind: MalformedKind::IfdEntry,
288 message: e.to_string(),
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn malformed_kind_is_copy_and_eq() {
299 let a = MalformedKind::JpegSegment;
300 let b = a;
301 assert_eq!(a, b);
302 }
303
304 #[test]
305 fn malformed_kind_covers_all_structural_units() {
306 for k in [
307 MalformedKind::JpegSegment,
308 MalformedKind::TiffHeader,
309 MalformedKind::IfdEntry,
310 MalformedKind::IsoBmffBox,
311 MalformedKind::EbmlElement,
312 ] {
313 let _ = format!("{k:?}");
314 }
315 }
316
317 #[test]
318 fn convert_error_displays_each_variant() {
319 let cases: &[(ConvertError, &str)] = &[
320 (
321 ConvertError::UnknownTagName("Foo".into()),
322 "unknown ExifTag name: Foo",
323 ),
324 (
325 ConvertError::InvalidIso6709("garbage".into()),
326 "invalid ISO 6709 coordinate: garbage",
327 ),
328 (
329 ConvertError::NegativeRational,
330 "rational has negative value",
331 ),
332 (
333 ConvertError::InvalidDecimalDegrees(f64::NAN),
334 "decimal degrees out of range or non-finite: NaN",
335 ),
336 ];
337 for (err, expected) in cases {
338 assert_eq!(err.to_string(), *expected);
339 }
340 }
341
342 #[test]
343 fn convert_error_does_not_convert_to_error() {
344 let _ = ConvertError::NegativeRational;
348 let _ = Error::UnsupportedFormat;
349 }
350
351 #[test]
352 fn error_io_from_io_error() {
353 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "x");
354 let err: Error = io_err.into();
355 assert!(matches!(err, Error::Io(_)));
356 }
357
358 #[test]
359 fn error_unsupported_format_displays() {
360 assert_eq!(
361 Error::UnsupportedFormat.to_string(),
362 "unsupported media format"
363 );
364 }
365
366 #[test]
367 fn error_exif_not_found_displays() {
368 assert_eq!(
369 Error::ExifNotFound.to_string(),
370 "no exif data found in this file"
371 );
372 }
373
374 #[test]
375 fn error_track_not_found_displays() {
376 assert_eq!(
377 Error::TrackNotFound.to_string(),
378 "no track info found in this file"
379 );
380 }
381
382 #[test]
383 fn error_malformed_displays() {
384 let e = Error::Malformed {
385 kind: MalformedKind::JpegSegment,
386 message: "bad SOI".into(),
387 };
388 assert_eq!(e.to_string(), "malformed jpeg segment: bad SOI");
389 }
390
391 #[test]
392 fn error_unexpected_eof_displays() {
393 let e = Error::UnexpectedEof {
394 context: "tiff header",
395 };
396 assert_eq!(
397 e.to_string(),
398 "unexpected end of input while parsing tiff header"
399 );
400 }
401
402 #[test]
403 fn entry_error_truncated_displays() {
404 let e = EntryError::Truncated {
405 needed: 8,
406 available: 4,
407 };
408 assert_eq!(
409 e.to_string(),
410 "entry truncated: needed 8 bytes, only 4 available"
411 );
412 }
413
414 #[test]
415 fn entry_error_invalid_shape_displays() {
416 let e = EntryError::InvalidShape {
417 format: 7,
418 count: 1,
419 };
420 assert_eq!(e.to_string(), "invalid entry shape: format=7, count=1");
421 }
422
423 #[test]
424 fn entry_error_invalid_value_displays() {
425 let e = EntryError::InvalidValue("not utf-8");
426 assert_eq!(e.to_string(), "invalid value: not utf-8");
427 }
428
429 #[test]
430 fn entry_error_into_error_routes_to_malformed_ifd_entry() {
431 let e = EntryError::Truncated {
432 needed: 8,
433 available: 4,
434 };
435 let err: Error = e.into();
436 match err {
437 Error::Malformed { kind, message } => {
438 assert_eq!(kind, MalformedKind::IfdEntry);
439 assert!(message.contains("entry truncated"));
440 }
441 other => panic!("unexpected variant: {other:?}"),
442 }
443 }
444}