Skip to main content

rar_stream/
error.rs

1//! Error types for RAR parsing and decompression.
2//!
3//! This module provides the [`RarError`] type which covers all possible errors
4//! that can occur when parsing, decompressing, or decrypting RAR archives.
5//!
6//! ## Error Categories
7//!
8//! | Category | Errors | Description |
9//! |----------|--------|-------------|
10//! | Format | [`InvalidSignature`], [`InvalidHeader`] | File is not a valid RAR archive |
11//! | Encryption | [`PasswordRequired`], [`DecryptionFailed`] | Encryption-related errors |
12//! | Decompression | [`DecompressionNotSupported`] | Unsupported compression method |
13//! | I/O | [`Io`], [`BufferTooSmall`] | Read/write errors |
14//!
15//! ## Example
16//!
17//! ```rust,ignore
18//! use rar_stream::{RarFilesPackage, RarError};
19//!
20//! match package.parse(opts).await {
21//!     Ok(files) => println!("Found {} files", files.len()),
22//!     Err(RarError::InvalidSignature) => eprintln!("Not a RAR file"),
23//!     Err(RarError::PasswordRequired) => eprintln!("Archive is encrypted"),
24//!     Err(e) => eprintln!("Error: {}", e),
25//! }
26//! ```
27//!
28//! [`InvalidSignature`]: RarError::InvalidSignature
29//! [`InvalidHeader`]: RarError::InvalidHeader
30//! [`PasswordRequired`]: RarError::PasswordRequired
31//! [`DecryptionFailed`]: RarError::DecryptionFailed
32//! [`DecompressionNotSupported`]: RarError::DecompressionNotSupported
33//! [`Io`]: RarError::Io
34//! [`BufferTooSmall`]: RarError::BufferTooSmall
35
36use std::fmt;
37use std::io;
38
39/// Error type for RAR operations.
40///
41/// This enum covers all possible errors that can occur when parsing,
42/// decompressing, or decrypting RAR archives. It implements [`std::error::Error`]
43/// for integration with the Rust error handling ecosystem.
44///
45/// # Example
46///
47/// ```rust,ignore
48/// use rar_stream::RarError;
49///
50/// fn handle_error(err: RarError) {
51///     match err {
52///         RarError::InvalidSignature => {
53///             // File doesn't start with RAR magic bytes
54///         }
55///         RarError::PasswordRequired => {
56///             // Need to provide password in ParseOptions
57///         }
58///         RarError::Io(io_err) => {
59///             // Underlying I/O error (file not found, permission denied, etc.)
60///         }
61///         _ => {}
62///     }
63/// }
64/// ```
65#[derive(Debug)]
66pub enum RarError {
67    /// The file does not have a valid RAR signature.
68    ///
69    /// RAR files must start with either:
70    /// - RAR4: `Rar!\x1a\x07\x00` (7 bytes)
71    /// - RAR5: `Rar!\x1a\x07\x01\x00` (8 bytes)
72    InvalidSignature,
73
74    /// A header in the archive is malformed or corrupt.
75    ///
76    /// This usually indicates file corruption or an incomplete download.
77    InvalidHeader,
78
79    /// An unknown or unsupported header type was encountered.
80    ///
81    /// The `u8` value is the header type byte. Standard types are:
82    /// - `0x72` (114): Marker header
83    /// - `0x73` (115): Archive header
84    /// - `0x74` (116): File header
85    /// - `0x7B` (123): End of archive
86    InvalidHeaderType(u8),
87
88    /// The compression method is not supported.
89    ///
90    /// The `u8` value is the method byte:
91    /// - `0x30`: Store (no compression) - always supported
92    /// - `0x31`-`0x35`: LZSS variants - supported
93    /// - `0x36`+: Future methods - may not be supported
94    DecompressionNotSupported(u8),
95
96    /// The archive is encrypted but the `crypto` feature is not enabled.
97    ///
98    /// Enable the `crypto` feature in Cargo.toml:
99    /// ```toml
100    /// rar-stream = { version = "4", features = ["async", "crypto"] }
101    /// ```
102    EncryptedNotSupported,
103
104    /// The archive is encrypted but no password was provided.
105    ///
106    /// Provide a password in [`ParseOptions`]:
107    /// ```rust,ignore
108    /// let opts = ParseOptions {
109    ///     password: Some("secret".to_string()),
110    ///     ..Default::default()
111    /// };
112    /// ```
113    ///
114    /// [`ParseOptions`]: crate::ParseOptions
115    PasswordRequired,
116
117    /// Decryption failed (wrong password or corrupt data).
118    ///
119    /// The `String` contains additional context about the failure.
120    /// Common causes:
121    /// - Incorrect password
122    /// - Corrupt encrypted data
123    /// - Truncated archive
124    DecryptionFailed(String),
125
126    /// The provided buffer is too small.
127    ///
128    /// This occurs when reading into a fixed-size buffer that cannot
129    /// hold the required data.
130    BufferTooSmall {
131        /// Number of bytes needed.
132        needed: usize,
133        /// Number of bytes available.
134        have: usize,
135    },
136
137    /// An invalid file offset was requested.
138    ///
139    /// This occurs when seeking beyond the end of a file or archive.
140    InvalidOffset {
141        /// The requested offset.
142        offset: u64,
143        /// The actual file length.
144        length: u64,
145    },
146
147    /// An I/O error occurred.
148    ///
149    /// Wraps [`std::io::Error`] for file system operations.
150    Io(io::Error),
151
152    /// No files were found in the archive.
153    ///
154    /// The archive may be empty, or all files may have been filtered out.
155    NoFilesFound,
156
157    /// RAR5 format detected but a specific feature is not supported.
158    ///
159    /// This is a legacy error that should rarely occur with current versions.
160    Rar5NotFullySupported,
161
162    /// The archive has encrypted headers and requires a password to list files.
163    ///
164    /// RAR5 archives created with `rar -hp` encrypt both file data and headers.
165    /// Without the correct password, even file names cannot be read.
166    #[cfg(feature = "crypto")]
167    EncryptedHeaders,
168}
169
170impl fmt::Display for RarError {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            Self::InvalidSignature => write!(f, "Invalid RAR signature"),
174            Self::InvalidHeader => write!(f, "Invalid or malformed header"),
175            Self::InvalidHeaderType(t) => write!(f, "Invalid header type: {}", t),
176            Self::DecompressionNotSupported(m) => {
177                write!(f, "Decompression not supported (method: 0x{:02x})", m)
178            }
179            Self::EncryptedNotSupported => write!(f, "Encrypted archives not supported"),
180            Self::PasswordRequired => write!(f, "Password required for encrypted file"),
181            Self::DecryptionFailed(msg) => write!(f, "Decryption failed: {}", msg),
182            Self::BufferTooSmall { needed, have } => {
183                write!(f, "Buffer too small: need {} bytes, have {}", needed, have)
184            }
185            Self::InvalidOffset { offset, length } => {
186                write!(f, "Invalid offset: {} (file length: {})", offset, length)
187            }
188            Self::Io(e) => write!(f, "IO error: {}", e),
189            Self::NoFilesFound => write!(f, "No files found in archive"),
190            Self::Rar5NotFullySupported => {
191                write!(
192                    f,
193                    "RAR5 format detected but decompression not yet supported"
194                )
195            }
196            #[cfg(feature = "crypto")]
197            Self::EncryptedHeaders => {
198                write!(
199                    f,
200                    "Archive has encrypted headers, password required to list files"
201                )
202            }
203        }
204    }
205}
206
207impl std::error::Error for RarError {
208    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
209        match self {
210            Self::Io(e) => Some(e),
211            _ => None,
212        }
213    }
214}
215
216impl From<io::Error> for RarError {
217    fn from(e: io::Error) -> Self {
218        Self::Io(e)
219    }
220}
221
222impl From<crate::decompress::DecompressError> for RarError {
223    fn from(e: crate::decompress::DecompressError) -> Self {
224        match e {
225            crate::decompress::DecompressError::UnsupportedMethod(m) => {
226                Self::DecompressionNotSupported(m)
227            }
228            _ => Self::Io(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
229        }
230    }
231}
232
233pub type Result<T> = std::result::Result<T, RarError>;