lofty/
error.rs

1//! Contains the errors that can arise within Lofty
2//!
3//! The primary error is [`LoftyError`]. The type of error is determined by [`ErrorKind`],
4//! which can be extended at any time.
5
6use crate::file::FileType;
7use crate::id3::v2::FrameId;
8use crate::tag::ItemKey;
9
10use std::collections::TryReserveError;
11use std::fmt::{Debug, Display, Formatter};
12
13use ogg_pager::PageError;
14
15/// Alias for `Result<T, LoftyError>`
16pub type Result<T> = std::result::Result<T, LoftyError>;
17
18/// The types of errors that can occur
19#[derive(Debug)]
20#[non_exhaustive]
21pub enum ErrorKind {
22	// File format related errors
23	/// Unable to guess the format
24	UnknownFormat,
25
26	// File data related errors
27	/// Attempting to read/write an abnormally large amount of data
28	TooMuchData,
29	/// Expected the data to be a different size than provided
30	///
31	/// This occurs when the size of an item is written as one value, but that size is either too
32	/// big or small to be valid within the bounds of that item.
33	// TODO: Should probably have context
34	SizeMismatch,
35	/// Errors that occur while decoding a file
36	FileDecoding(FileDecodingError),
37	/// Errors that occur while encoding a file
38	FileEncoding(FileEncodingError),
39
40	// Picture related errors
41	/// Provided an invalid picture
42	NotAPicture,
43	/// Attempted to write a picture that the format does not support
44	UnsupportedPicture,
45
46	// Tag related errors
47	/// Arises when writing a tag to a file type that doesn't support it
48	UnsupportedTag,
49	/// Arises when a tag is expected (Ex. found an "ID3 " chunk in a WAV file), but isn't found
50	FakeTag,
51	/// Errors that arise while decoding text
52	TextDecode(&'static str),
53	/// Arises when decoding OR encoding a problematic [`Timestamp`](crate::tag::items::Timestamp)
54	BadTimestamp(&'static str),
55	/// Errors that arise while reading/writing ID3v2 tags
56	Id3v2(Id3v2Error),
57
58	/// Arises when an atom contains invalid data
59	BadAtom(&'static str),
60	/// Arises when attempting to use [`Atom::merge`](crate::mp4::Atom::merge) with mismatching identifiers
61	AtomMismatch,
62
63	// Conversions for external errors
64	/// Errors that arise while parsing OGG pages
65	OggPage(ogg_pager::PageError),
66	/// Unable to convert bytes to a String
67	StringFromUtf8(std::string::FromUtf8Error),
68	/// Unable to convert bytes to a str
69	StrFromUtf8(std::str::Utf8Error),
70	/// Represents all cases of [`std::io::Error`].
71	Io(std::io::Error),
72	/// Represents all cases of [`std::fmt::Error`].
73	Fmt(std::fmt::Error),
74	/// Failure to allocate enough memory
75	Alloc(TryReserveError),
76	/// This should **never** be encountered
77	Infallible(std::convert::Infallible),
78}
79
80/// The types of errors that can occur while interacting with ID3v2 tags
81#[derive(Debug)]
82#[non_exhaustive]
83pub enum Id3v2ErrorKind {
84	// Header
85	/// Arises when an invalid ID3v2 version is found
86	BadId3v2Version(u8, u8),
87	/// Arises when a compressed ID3v2.2 tag is encountered
88	///
89	/// At the time the ID3v2.2 specification was written, a compression scheme wasn't decided.
90	/// As such, it is recommended to ignore the tag entirely.
91	V2Compression,
92	/// Arises when an extended header has an invalid size (must be >= 6 bytes and less than the total tag size)
93	BadExtendedHeaderSize,
94
95	// Frame
96	/// Arises when a frame ID contains invalid characters (must be within `'A'..'Z'` or `'0'..'9'`)
97	/// or if the ID is too short/long.
98	BadFrameId(Vec<u8>),
99	/// Arises when no frame ID is available in the ID3v2 specification for an item key
100	/// and the associated value type.
101	UnsupportedFrameId(ItemKey),
102	/// Arises when a frame doesn't have enough data
103	BadFrameLength,
104	/// Arises when a frame with no content is parsed with [ParsingMode::Strict](crate::config::ParsingMode::Strict)
105	EmptyFrame(FrameId<'static>),
106	/// Arises when reading/writing a compressed or encrypted frame with no data length indicator
107	MissingDataLengthIndicator,
108	/// Arises when a frame or tag has its unsynchronisation flag set, but the content is not actually synchsafe
109	///
110	/// See [`FrameFlags::unsynchronisation`](crate::id3::v2::FrameFlags::unsynchronisation) for an explanation.
111	InvalidUnsynchronisation,
112	/// Arises when a text encoding other than Latin-1 or UTF-16 appear in an ID3v2.2 tag
113	V2InvalidTextEncoding,
114	/// Arises when an invalid picture format is parsed. Only applicable to [`ID3v2Version::V2`](crate::id3::v2::Id3v2Version::V2)
115	BadPictureFormat(String),
116	/// Arises when invalid data is encountered while reading an ID3v2 synchronized text frame
117	BadSyncText,
118	/// Arises when decoding a [`UniqueFileIdentifierFrame`](crate::id3::v2::UniqueFileIdentifierFrame) with no owner
119	MissingUfidOwner,
120	/// Arises when decoding a [`RelativeVolumeAdjustmentFrame`](crate::id3::v2::RelativeVolumeAdjustmentFrame) with an invalid channel type
121	BadRva2ChannelType,
122	/// Arises when decoding a [`TimestampFormat`](crate::id3::v2::TimestampFormat) with an invalid type
123	BadTimestampFormat,
124
125	// Compression
126	#[cfg(feature = "id3v2_compression_support")]
127	/// Arises when a compressed frame is unable to be decompressed
128	Decompression(flate2::DecompressError),
129	#[cfg(not(feature = "id3v2_compression_support"))]
130	/// Arises when a compressed frame is encountered, but support is disabled
131	CompressedFrameEncountered,
132
133	// Writing
134	/// Arises when attempting to write an encrypted frame with an invalid encryption method symbol (must be <= 0x80)
135	InvalidEncryptionMethodSymbol(u8),
136	/// Arises when attempting to write an invalid Frame (Bad `FrameId`/`FrameValue` pairing)
137	BadFrame(String, &'static str),
138	/// Arises when attempting to write a [`CommentFrame`](crate::id3::v2::CommentFrame) or [`UnsynchronizedTextFrame`](crate::id3::v2::UnsynchronizedTextFrame) with an invalid language
139	InvalidLanguage([u8; 3]),
140}
141
142impl Display for Id3v2ErrorKind {
143	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
144		match self {
145			// Header
146			Self::BadId3v2Version(major, minor) => write!(
147				f,
148				"Found an invalid version (v{major}.{minor}), expected any major revision in: (2, \
149				 3, 4)"
150			),
151			Self::V2Compression => write!(f, "Encountered a compressed ID3v2.2 tag"),
152			Self::BadExtendedHeaderSize => {
153				write!(f, "Found an extended header with an invalid size")
154			},
155
156			// Frame
157			Self::BadFrameId(frame_id) => write!(f, "Failed to parse a frame ID: 0x{frame_id:x?}"),
158			Self::UnsupportedFrameId(item_key) => {
159				write!(f, "Unsupported frame ID for item key {item_key:?}")
160			},
161			Self::BadFrameLength => write!(
162				f,
163				"Frame isn't long enough to extract the necessary information"
164			),
165			Self::EmptyFrame(id) => write!(f, "Frame `{id}` is empty"),
166			Self::MissingDataLengthIndicator => write!(
167				f,
168				"Encountered an encrypted frame without a data length indicator"
169			),
170			Self::InvalidUnsynchronisation => write!(f, "Encountered an invalid unsynchronisation"),
171			Self::V2InvalidTextEncoding => {
172				write!(f, "ID3v2.2 only supports Latin-1 and UTF-16 encodings")
173			},
174			Self::BadPictureFormat(format) => {
175				write!(f, "Picture: Found unexpected format \"{format}\"")
176			},
177			Self::BadSyncText => write!(f, "Encountered invalid data in SYLT frame"),
178			Self::MissingUfidOwner => write!(f, "Missing owner in UFID frame"),
179			Self::BadRva2ChannelType => write!(f, "Encountered invalid channel type in RVA2 frame"),
180			Self::BadTimestampFormat => write!(
181				f,
182				"Encountered an invalid timestamp format in a synchronized frame"
183			),
184
185			// Compression
186			#[cfg(feature = "id3v2_compression_support")]
187			Self::Decompression(err) => write!(f, "Failed to decompress frame: {err}"),
188			#[cfg(not(feature = "id3v2_compression_support"))]
189			Self::CompressedFrameEncountered => write!(
190				f,
191				"Encountered a compressed ID3v2 frame, support is disabled"
192			),
193
194			// Writing
195			Self::InvalidEncryptionMethodSymbol(symbol) => write!(
196				f,
197				"Attempted to write an encrypted frame with an invalid method symbol ({symbol})"
198			),
199			Self::BadFrame(frame_id, frame_value) => write!(
200				f,
201				"Attempted to write an invalid frame. ID: \"{frame_id}\", Value: \"{frame_value}\"",
202			),
203			Self::InvalidLanguage(lang) => write!(
204				f,
205				"Invalid frame language found: {lang:?} (expected 3 ascii characters)"
206			),
207		}
208	}
209}
210
211/// An error that arises while interacting with an ID3v2 tag
212pub struct Id3v2Error {
213	kind: Id3v2ErrorKind,
214}
215
216impl Id3v2Error {
217	/// Create a new `ID3v2Error` from an [`Id3v2ErrorKind`]
218	#[must_use]
219	pub const fn new(kind: Id3v2ErrorKind) -> Self {
220		Self { kind }
221	}
222
223	/// Returns the [`Id3v2ErrorKind`]
224	pub fn kind(&self) -> &Id3v2ErrorKind {
225		&self.kind
226	}
227}
228
229impl Debug for Id3v2Error {
230	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231		write!(f, "ID3v2: {:?}", self.kind)
232	}
233}
234
235impl Display for Id3v2Error {
236	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
237		write!(f, "ID3v2: {}", self.kind)
238	}
239}
240
241/// An error that arises while decoding a file
242pub struct FileDecodingError {
243	format: Option<FileType>,
244	description: &'static str,
245}
246
247impl FileDecodingError {
248	/// Create a `FileDecodingError` from a [`FileType`] and description
249	#[must_use]
250	pub const fn new(format: FileType, description: &'static str) -> Self {
251		Self {
252			format: Some(format),
253			description,
254		}
255	}
256
257	/// Create a `FileDecodingError` without binding it to a [`FileType`]
258	pub fn from_description(description: &'static str) -> Self {
259		Self {
260			format: None,
261			description,
262		}
263	}
264
265	/// Returns the associated [`FileType`], if one exists
266	pub fn format(&self) -> Option<FileType> {
267		self.format
268	}
269
270	/// Returns the error description
271	pub fn description(&self) -> &str {
272		self.description
273	}
274}
275
276impl Debug for FileDecodingError {
277	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
278		if let Some(format) = self.format {
279			write!(f, "{:?}: {:?}", format, self.description)
280		} else {
281			write!(f, "{:?}", self.description)
282		}
283	}
284}
285
286impl Display for FileDecodingError {
287	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
288		if let Some(format) = self.format {
289			write!(f, "{:?}: {}", format, self.description)
290		} else {
291			write!(f, "{}", self.description)
292		}
293	}
294}
295
296/// An error that arises while encoding a file
297pub struct FileEncodingError {
298	format: Option<FileType>,
299	description: &'static str,
300}
301
302impl FileEncodingError {
303	/// Create a `FileEncodingError` from a [`FileType`] and description
304	///
305	/// # Examples
306	///
307	/// ```rust
308	/// use lofty::error::FileEncodingError;
309	/// use lofty::file::FileType;
310	///
311	/// // This error is bounded to `FileType::Mpeg`, which will be displayed when the error is formatted
312	/// let mpeg_error =
313	/// 	FileEncodingError::new(FileType::Mpeg, "Something went wrong in the MPEG file!");
314	/// ```
315	#[must_use]
316	pub const fn new(format: FileType, description: &'static str) -> Self {
317		Self {
318			format: Some(format),
319			description,
320		}
321	}
322
323	/// Create a `FileEncodingError` without binding it to a [`FileType`]
324	///
325	/// # Examples
326	///
327	/// ```rust
328	/// use lofty::error::FileEncodingError;
329	/// use lofty::file::FileType;
330	///
331	/// // The error isn't bounded to FileType::Mpeg, only the message will be displayed when the
332	/// // error is formatted
333	/// let mpeg_error = FileEncodingError::from_description("Something went wrong in the MPEG file!");
334	/// ```
335	pub fn from_description(description: &'static str) -> Self {
336		Self {
337			format: None,
338			description,
339		}
340	}
341
342	/// Returns the associated [`FileType`], if one exists
343	///
344	/// # Examples
345	///
346	/// ```rust
347	/// use lofty::error::FileEncodingError;
348	/// use lofty::file::FileType;
349	///
350	/// let mpeg_error =
351	/// 	FileEncodingError::new(FileType::Mpeg, "Something went wrong in the MPEG file!");
352	///
353	/// assert_eq!(mpeg_error.format(), Some(FileType::Mpeg));
354	/// ```
355	pub fn format(&self) -> Option<FileType> {
356		self.format
357	}
358
359	/// Returns the error description
360	///
361	/// # Examples
362	///
363	/// ```rust
364	/// use lofty::error::FileEncodingError;
365	/// use lofty::file::FileType;
366	///
367	/// let mpeg_error =
368	/// 	FileEncodingError::new(FileType::Mpeg, "Something went wrong in the MPEG file!");
369	///
370	/// assert_eq!(
371	/// 	mpeg_error.description(),
372	/// 	"Something went wrong in the MPEG file!"
373	/// );
374	/// ```
375	pub fn description(&self) -> &str {
376		self.description
377	}
378}
379
380impl Debug for FileEncodingError {
381	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
382		if let Some(format) = self.format {
383			write!(f, "{:?}: {:?}", format, self.description)
384		} else {
385			write!(f, "{:?}", self.description)
386		}
387	}
388}
389
390impl Display for FileEncodingError {
391	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
392		if let Some(format) = self.format {
393			write!(f, "{:?}: {:?}", format, self.description)
394		} else {
395			write!(f, "{}", self.description)
396		}
397	}
398}
399
400/// Errors that could occur within Lofty
401pub struct LoftyError {
402	pub(crate) kind: ErrorKind,
403}
404
405impl LoftyError {
406	/// Create a `LoftyError` from an [`ErrorKind`]
407	///
408	/// # Examples
409	///
410	/// ```rust
411	/// use lofty::error::{ErrorKind, LoftyError};
412	///
413	/// let unknown_format = LoftyError::new(ErrorKind::UnknownFormat);
414	/// ```
415	#[must_use]
416	pub const fn new(kind: ErrorKind) -> Self {
417		Self { kind }
418	}
419
420	/// Returns the [`ErrorKind`]
421	///
422	/// # Examples
423	///
424	/// ```rust
425	/// use lofty::error::{ErrorKind, LoftyError};
426	///
427	/// let unknown_format = LoftyError::new(ErrorKind::UnknownFormat);
428	/// if let ErrorKind::UnknownFormat = unknown_format.kind() {
429	/// 	println!("What's the format?");
430	/// }
431	/// ```
432	pub fn kind(&self) -> &ErrorKind {
433		&self.kind
434	}
435}
436
437impl std::error::Error for LoftyError {}
438
439impl Debug for LoftyError {
440	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
441		write!(f, "{:?}", self.kind)
442	}
443}
444
445impl From<Id3v2Error> for LoftyError {
446	fn from(input: Id3v2Error) -> Self {
447		Self {
448			kind: ErrorKind::Id3v2(input),
449		}
450	}
451}
452
453impl From<FileDecodingError> for LoftyError {
454	fn from(input: FileDecodingError) -> Self {
455		Self {
456			kind: ErrorKind::FileDecoding(input),
457		}
458	}
459}
460
461impl From<FileEncodingError> for LoftyError {
462	fn from(input: FileEncodingError) -> Self {
463		Self {
464			kind: ErrorKind::FileEncoding(input),
465		}
466	}
467}
468
469impl From<ogg_pager::PageError> for LoftyError {
470	fn from(input: PageError) -> Self {
471		Self {
472			kind: ErrorKind::OggPage(input),
473		}
474	}
475}
476
477impl From<std::io::Error> for LoftyError {
478	fn from(input: std::io::Error) -> Self {
479		Self {
480			kind: ErrorKind::Io(input),
481		}
482	}
483}
484
485impl From<std::fmt::Error> for LoftyError {
486	fn from(input: std::fmt::Error) -> Self {
487		Self {
488			kind: ErrorKind::Fmt(input),
489		}
490	}
491}
492
493impl From<std::string::FromUtf8Error> for LoftyError {
494	fn from(input: std::string::FromUtf8Error) -> Self {
495		Self {
496			kind: ErrorKind::StringFromUtf8(input),
497		}
498	}
499}
500
501impl From<std::str::Utf8Error> for LoftyError {
502	fn from(input: std::str::Utf8Error) -> Self {
503		Self {
504			kind: ErrorKind::StrFromUtf8(input),
505		}
506	}
507}
508
509impl From<std::collections::TryReserveError> for LoftyError {
510	fn from(input: TryReserveError) -> Self {
511		Self {
512			kind: ErrorKind::Alloc(input),
513		}
514	}
515}
516
517impl From<std::convert::Infallible> for LoftyError {
518	fn from(input: std::convert::Infallible) -> Self {
519		Self {
520			kind: ErrorKind::Infallible(input),
521		}
522	}
523}
524
525impl Display for LoftyError {
526	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
527		match self.kind {
528			// Conversions
529			ErrorKind::OggPage(ref err) => write!(f, "{err}"),
530			ErrorKind::StringFromUtf8(ref err) => write!(f, "{err}"),
531			ErrorKind::StrFromUtf8(ref err) => write!(f, "{err}"),
532			ErrorKind::Io(ref err) => write!(f, "{err}"),
533			ErrorKind::Fmt(ref err) => write!(f, "{err}"),
534			ErrorKind::Alloc(ref err) => write!(f, "{err}"),
535
536			ErrorKind::UnknownFormat => {
537				write!(f, "No format could be determined from the provided file")
538			},
539			ErrorKind::NotAPicture => write!(f, "Picture: Encountered invalid data"),
540			ErrorKind::UnsupportedPicture => {
541				write!(f, "Picture: attempted to write an unsupported picture")
542			},
543			ErrorKind::UnsupportedTag => write!(
544				f,
545				"Attempted to write a tag to a format that does not support it"
546			),
547			ErrorKind::FakeTag => write!(f, "Reading: Expected a tag, found invalid data"),
548			ErrorKind::TextDecode(message) => write!(f, "Text decoding: {message}"),
549			ErrorKind::BadTimestamp(message) => {
550				write!(f, "Encountered an invalid timestamp: {message}")
551			},
552			ErrorKind::Id3v2(ref id3v2_err) => write!(f, "{id3v2_err}"),
553			ErrorKind::BadAtom(message) => write!(f, "MP4 Atom: {message}"),
554			ErrorKind::AtomMismatch => write!(
555				f,
556				"MP4 Atom: Attempted to use `Atom::merge()` with mismatching identifiers"
557			),
558
559			// Files
560			ErrorKind::TooMuchData => write!(
561				f,
562				"Attempted to read/write an abnormally large amount of data"
563			),
564			ErrorKind::SizeMismatch => write!(
565				f,
566				"Encountered an invalid item size, either too big or too small to be valid"
567			),
568			ErrorKind::FileDecoding(ref file_decode_err) => write!(f, "{file_decode_err}"),
569			ErrorKind::FileEncoding(ref file_encode_err) => write!(f, "{file_encode_err}"),
570
571			ErrorKind::Infallible(_) => write!(f, "A expected condition was not upheld"),
572		}
573	}
574}