Skip to main content

lib_epub/
error.rs

1//! Error Type Definition Module
2//!
3//! This module defines the various error types that may be encountered during
4//! EPUB file parsing and processing. All errors are uniformly wrapped in the
5//! `EpubError` enumeration for convenient error handling by the caller.
6//!
7//! ## Main Error Types
8//!
9//! - [EpubError] - Enumeration of main errors during EPUB processing
10//! - [EpubBuilderError] - Specific errors during EPUB build process (requires `builder` functionality enabled)
11
12use thiserror::Error;
13
14/// Types of errors that can occur during EPUB processing
15///
16/// This enumeration defines the various error cases that can be encountered
17/// when parsing and processing EPUB files, including file format errors,
18/// missing resources, compression issues, etc.
19#[derive(Debug, Error)]
20pub enum EpubError {
21    /// ZIP archive related errors
22    ///
23    /// Errors occur when processing the ZIP structure of EPUB files,
24    /// such as file corruption, unreadability, etc.
25    #[error("Archive error: {source}")]
26    ArchiveError { source: zip::result::ZipError },
27
28    /// Data Decoding Error - Null dataw
29    ///
30    /// This error occurs when trying to decode an empty stream.
31    #[error("Decode error: The data is empty.")]
32    EmptyDataError,
33
34    #[cfg(feature = "builder")]
35    #[error("Epub builder error: {source}")]
36    EpubBuilderError { source: EpubBuilderError },
37
38    /// XML parsing failure error
39    ///
40    /// This error usually only occurs when there is an exception in the XML parsing process,
41    /// the event listener ends abnormally, resulting in the root node not being initialized.
42    /// This exception may be caused by an incorrect XML file.
43    #[error(
44        "Failed parsing XML error: Unknown problems occurred during XML parsing, causing parsing failure."
45    )]
46    FailedParsingXml,
47
48    #[error("IO error: {source}")]
49    IOError { source: std::io::Error },
50
51    /// Missing required attribute error
52    ///
53    /// Triggered when an XML element in an EPUB file lacks the required
54    /// attributes required by the EPUB specification.
55    #[error(
56        "Missing required attribute: The \"{attribute}\" attribute is a must attribute for the \"{tag}\" element."
57    )]
58    MissingRequiredAttribute { tag: String, attribute: String },
59
60    /// Mutex error
61    ///
62    /// This error occurs when a mutex is poisoned, which means
63    /// that a thread has panicked while holding a lock on the mutex.
64    #[error("Mutex error: Mutex was poisoned.")]
65    MutexError,
66
67    /// Non-canonical EPUB structure error
68    ///
69    /// This error occurs when an EPUB file lacks some files or directory
70    /// structure that is required in EPUB specification.
71    #[error("Non-canonical epub: The \"{expected_file}\" file was not found.")]
72    NonCanonicalEpub { expected_file: String },
73
74    /// Non-canonical file structure error
75    ///
76    /// This error is triggered when the required XML elements in the
77    /// specification are missing from the EPUB file.
78    #[error("Non-canonical file: The \"{tag}\" elements was not found.")]
79    NonCanonicalFile { tag: String },
80
81    /// Missing supported file format error
82    ///
83    /// This error occurs when trying to get a resource but there isn't any supported file format.
84    /// It usually happens when there are no supported formats available in the fallback chain.
85    #[error(
86        "No supported file format: The fallback resource does not contain the file format you support."
87    )]
88    NoSupportedFileFormat,
89
90    /// Relative link leak error
91    ///
92    /// This error occurs when a relative path link is outside the scope
93    /// of an EPUB container, which is a security protection mechanism.
94    #[error("Relative link leakage: Path \"{path}\" is out of container range.")]
95    RealtiveLinkLeakage { path: String },
96
97    /// Unable to find the resource id error
98    ///
99    /// This error occurs when trying to get a resource by id but that id doesn't exist in the manifest.
100    #[error("Resource Id Not Exist: There is no resource item with id \"{id}\".")]
101    ResourceIdNotExist { id: String },
102
103    /// Unable to find the resource error
104    ///
105    /// This error occurs when an attempt is made to get a resource
106    /// but it does not exist in the EPUB container.
107    #[error("Resource not found: Unable to find resource from \"{resource}\".")]
108    ResourceNotFound { resource: String },
109
110    /// Unrecognized EPUB version error
111    ///
112    /// This error occurs when parsing epub files, the library cannot
113    /// directly or indirectly identify the epub version number.
114    #[error(
115        "Unrecognized EPUB version: Unable to identify version number and version characteristics from epub file"
116    )]
117    UnrecognizedEpubVersion,
118
119    /// Unsupported encryption method error
120    ///
121    /// This error is triggered when attempting to decrypt a resource that uses
122    /// an encryption method not supported by this library.
123    ///
124    /// Currently, this library only supports:
125    /// - IDPF Font Obfuscation
126    /// - Adobe Font Obfuscation
127    #[error("Unsupported encryption method: The \"{method}\" encryption method is not supported.")]
128    UnsupportedEncryptedMethod { method: String },
129
130    /// Unusable compression method error
131    ///
132    /// This error occurs when an EPUB file uses an unsupported compression method.
133    #[error(
134        "Unusable compression method: The \"{file}\" file uses the unsupported \"{method}\" compression method."
135    )]
136    UnusableCompressionMethod { file: String, method: String },
137
138    /// UTF-8 decoding error
139    ///
140    /// This error occurs when attempting to decode byte data into a UTF-8 string
141    /// but the data is not formatted correctly.
142    #[error("Decode error: {source}")]
143    Utf8DecodeError { source: std::string::FromUtf8Error },
144
145    /// UTF-16 decoding error
146    ///
147    /// This error occurs when attempting to decode byte data into a UTF-16 string
148    /// but the data is not formatted correctly.
149    #[error("Decode error: {source}")]
150    Utf16DecodeError { source: std::string::FromUtf16Error },
151
152    /// WalkDir error
153    ///
154    /// This error occurs when using the WalkDir library to traverse the directory.
155    #[cfg(feature = "builder")]
156    #[error("WalkDir error: {source}")]
157    WalkDirError { source: walkdir::Error },
158
159    /// QuickXml error
160    ///
161    /// This error occurs when parsing XML data using the QuickXml library.
162    #[error("QuickXml error: {source}")]
163    QuickXmlError { source: quick_xml::Error },
164}
165
166impl From<zip::result::ZipError> for EpubError {
167    fn from(value: zip::result::ZipError) -> Self {
168        EpubError::ArchiveError { source: value }
169    }
170}
171
172impl From<quick_xml::Error> for EpubError {
173    fn from(value: quick_xml::Error) -> Self {
174        EpubError::QuickXmlError { source: value }
175    }
176}
177
178impl From<std::io::Error> for EpubError {
179    fn from(value: std::io::Error) -> Self {
180        EpubError::IOError { source: value }
181    }
182}
183
184impl From<std::string::FromUtf8Error> for EpubError {
185    fn from(value: std::string::FromUtf8Error) -> Self {
186        EpubError::Utf8DecodeError { source: value }
187    }
188}
189
190impl From<std::string::FromUtf16Error> for EpubError {
191    fn from(value: std::string::FromUtf16Error) -> Self {
192        EpubError::Utf16DecodeError { source: value }
193    }
194}
195
196impl<T> From<std::sync::PoisonError<T>> for EpubError {
197    fn from(_value: std::sync::PoisonError<T>) -> Self {
198        EpubError::MutexError
199    }
200}
201
202#[cfg(feature = "builder")]
203impl From<EpubBuilderError> for EpubError {
204    fn from(value: EpubBuilderError) -> Self {
205        EpubError::EpubBuilderError { source: value }
206    }
207}
208
209#[cfg(feature = "builder")]
210impl From<walkdir::Error> for EpubError {
211    fn from(value: walkdir::Error) -> Self {
212        EpubError::WalkDirError { source: value }
213    }
214}
215
216#[cfg(test)]
217impl PartialEq for EpubError {
218    fn eq(&self, other: &Self) -> bool {
219        match (self, other) {
220            (
221                Self::MissingRequiredAttribute { tag: l_tag, attribute: l_attribute },
222                Self::MissingRequiredAttribute { tag: r_tag, attribute: r_attribute },
223            ) => l_tag == r_tag && l_attribute == r_attribute,
224
225            (
226                Self::NonCanonicalEpub { expected_file: l_expected_file },
227                Self::NonCanonicalEpub { expected_file: r_expected_file },
228            ) => l_expected_file == r_expected_file,
229
230            (Self::NonCanonicalFile { tag: l_tag }, Self::NonCanonicalFile { tag: r_tag }) => {
231                l_tag == r_tag
232            }
233
234            (
235                Self::RealtiveLinkLeakage { path: l_path },
236                Self::RealtiveLinkLeakage { path: r_path },
237            ) => l_path == r_path,
238
239            (Self::ResourceIdNotExist { id: l_id }, Self::ResourceIdNotExist { id: r_id }) => {
240                l_id == r_id
241            }
242
243            (
244                Self::ResourceNotFound { resource: l_resource },
245                Self::ResourceNotFound { resource: r_resource },
246            ) => l_resource == r_resource,
247
248            (
249                Self::UnsupportedEncryptedMethod { method: l_method },
250                Self::UnsupportedEncryptedMethod { method: r_method },
251            ) => l_method == r_method,
252
253            (
254                Self::UnusableCompressionMethod { file: l_file, method: l_method },
255                Self::UnusableCompressionMethod { file: r_file, method: r_method },
256            ) => l_file == r_file && l_method == r_method,
257
258            (
259                Self::Utf8DecodeError { source: l_source },
260                Self::Utf8DecodeError { source: r_source },
261            ) => l_source == r_source,
262
263            (
264                Self::EpubBuilderError { source: l_source },
265                Self::EpubBuilderError { source: r_source },
266            ) => l_source == r_source,
267
268            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
269        }
270    }
271}
272
273/// Types of errors that can occur during EPUB build
274///
275/// This enumeration defines various error conditions that may occur
276/// when creating EPUB files using the `builder` function. These errors
277/// are typically related to EPUB specification requirements or validation
278/// rules during the build process.
279#[cfg(feature = "builder")]
280#[derive(Debug, Error)]
281#[cfg_attr(test, derive(PartialEq))]
282pub enum EpubBuilderError {
283    /// Illegal manifest path error
284    ///
285    /// This error is triggered when the path corresponding to a resource ID
286    /// in the manifest begins with "../". Using relative paths starting with "../"
287    /// when building the manifest fails to determine the 'current location',
288    /// making it impossible to pinpoint the resource.
289    #[error(
290        "A manifest with id '{manifest_id}' should not use a relative path starting with '../'."
291    )]
292    IllegalManifestPath { manifest_id: String },
293
294    /// Invalid rootfile path error
295    ///
296    /// This error is triggered when the rootfile path in the container.xml is invalid.
297    /// According to the EPUB specification, rootfile paths must be relative paths
298    /// that do not start with "../" to prevent directory traversal outside the EPUB container.
299    #[error("A rootfile path should be a relative path and not start with '../'.")]
300    IllegalRootfilePath,
301
302    /// Invalid footnote locate error
303    ///
304    /// This error is triggered when the footnote locate is out of range.
305    #[error("The footnote locate must be in the range of [0, {max_locate}].")]
306    InvalidFootnoteLocate { max_locate: usize },
307
308    /// Invalid mathml format error
309    ///
310    /// This error is triggered when parsing mathml fails.
311    #[error("{error}")]
312    InvalidMathMLFormat { error: String },
313
314    /// Invalid target path error
315    ///
316    /// This error is triggered when the target path terminates in a root or prefix,
317    /// or if it's the empty string.
318    #[error("The '{target_path}' target path is invalid.")]
319    InvalidTargetPath { target_path: String },
320
321    /// Manifest Circular Reference error
322    ///
323    /// This error is triggered when a fallback relationship between manifest items forms a cycle.
324    #[error("Circular reference detected in fallback chain for '{fallback_chain}'.")]
325    ManifestCircularReference { fallback_chain: String },
326
327    /// Manifest resource not found error
328    ///
329    /// This error is triggered when a manifest item specifies a fallback resource ID that does not exist.
330    #[error("Fallback resource '{manifest_id}' does not exist in manifest.")]
331    ManifestNotFound { manifest_id: String },
332
333    /// Missing necessary metadata error
334    ///
335    /// This error is triggered when the basic metadata required to build a valid EPUB is missing.
336    /// The following must be included: title, language, and an identifier with a 'pub-id' ID.
337    #[error("Requires at least one 'title', 'language', and 'identifier' with id 'pub-id'.")]
338    MissingNecessaryMetadata,
339
340    /// Missing necessary block data error
341    ///
342    /// This error is triggered when a block is missing necessary data.
343    #[error("The block '{block_type}' is missing necessary data '{missing_data}'")]
344    MissingNecessaryBlockData {
345        block_type: String,
346        missing_data: String,
347    },
348
349    /// Navigation information uninitialized error
350    ///
351    /// This error is triggered when attempting to build an EPUB but without setting navigation information.
352    #[error("Navigation information is not set.")]
353    NavigationInfoUninitalized,
354
355    /// Not expected file format error
356    ///
357    /// This error is triggered when build a `Blocl` with unmatched file format.
358    #[error("The file format is not current block expected.")]
359    NotExpectedFileFormat,
360
361    /// Missing rootfile error
362    ///
363    /// This error is triggered when attempting to build an EPUB without adding any 'rootfile'.
364    #[error("Need at least one rootfile.")]
365    MissingRootfile,
366
367    /// Target is not a file error
368    ///
369    /// This error is triggered when the specified target path is not a file.
370    #[error("Expect a file, but '{target_path}' is not a file.")]
371    TargetIsNotFile { target_path: String },
372
373    /// Too many nav flags error
374    ///
375    /// This error is triggered when the manifest contains multiple items with
376    /// the `nav` attribute. The EPUB specification requires that each EPUB have
377    /// **only one** navigation document.
378    #[error("There are too many items with 'nav' property in the manifest.")]
379    TooManyNavFlags,
380
381    /// Unknown file format error
382    ///
383    /// This error is triggered when the format type of the specified file cannot be analyzed.
384    #[error("Unable to analyze the file '{file_path}' type.")]
385    UnknownFileFormat { file_path: String },
386}