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