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