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>;