unity_asset_binary/
error.rs

1//! Error types for Unity binary parsing
2
3use thiserror::Error;
4
5/// Result type for Unity binary operations
6pub type Result<T> = std::result::Result<T, BinaryError>;
7
8/// Errors that can occur during Unity binary parsing
9#[derive(Error, Debug)]
10pub enum BinaryError {
11    /// I/O errors
12    #[error("I/O error: {0}")]
13    Io(#[from] std::io::Error),
14
15    /// Invalid file format
16    #[error("Invalid file format: {0}")]
17    InvalidFormat(String),
18
19    /// Unsupported file version
20    #[error("Unsupported file version: {0}")]
21    UnsupportedVersion(String),
22
23    /// Unsupported compression format
24    #[error("Unsupported compression: {0}")]
25    UnsupportedCompression(String),
26
27    /// Decompression failed
28    #[error("Decompression failed: {0}")]
29    DecompressionFailed(String),
30
31    /// Invalid data
32    #[error("Invalid data: {0}")]
33    InvalidData(String),
34
35    /// Parsing error
36    #[error("Parse error: {0}")]
37    ParseError(String),
38
39    /// Not enough data
40    #[error("Not enough data: expected {expected}, got {actual}")]
41    NotEnoughData { expected: usize, actual: usize },
42
43    /// Invalid signature
44    #[error("Invalid signature: expected {expected}, got {actual}")]
45    InvalidSignature { expected: String, actual: String },
46
47    /// Unsupported feature
48    #[error("Unsupported feature: {0}")]
49    Unsupported(String),
50
51    /// Memory allocation error
52    #[error("Memory allocation error: {0}")]
53    MemoryError(String),
54
55    /// Timeout error
56    #[error("Operation timed out: {0}")]
57    Timeout(String),
58
59    /// Resource limit exceeded
60    #[error("Resource limit exceeded: {0}")]
61    ResourceLimitExceeded(String),
62
63    /// Corrupted data
64    #[error("Corrupted data detected: {0}")]
65    CorruptedData(String),
66
67    /// Version compatibility error
68    #[error("Version compatibility error: {0}")]
69    VersionCompatibility(String),
70
71    /// Generic error with context
72    #[error("Error: {0}")]
73    Generic(String),
74}
75
76impl BinaryError {
77    /// Create a new invalid format error
78    pub fn invalid_format<S: Into<String>>(msg: S) -> Self {
79        Self::InvalidFormat(msg.into())
80    }
81
82    /// Create a generic error (for compatibility)
83    pub fn format<S: Into<String>>(msg: S) -> Self {
84        Self::Generic(msg.into())
85    }
86
87    /// Create a new unsupported version error
88    pub fn unsupported_version<S: Into<String>>(version: S) -> Self {
89        Self::UnsupportedVersion(version.into())
90    }
91
92    /// Create a new unsupported compression error
93    pub fn unsupported_compression<S: Into<String>>(compression: S) -> Self {
94        Self::UnsupportedCompression(compression.into())
95    }
96
97    /// Create a new decompression failed error
98    pub fn decompression_failed<S: Into<String>>(msg: S) -> Self {
99        Self::DecompressionFailed(msg.into())
100    }
101
102    /// Create a new invalid data error
103    pub fn invalid_data<S: Into<String>>(msg: S) -> Self {
104        Self::InvalidData(msg.into())
105    }
106
107    /// Create a new parse error
108    pub fn parse_error<S: Into<String>>(msg: S) -> Self {
109        Self::ParseError(msg.into())
110    }
111
112    /// Create a new not enough data error
113    pub fn not_enough_data(expected: usize, actual: usize) -> Self {
114        Self::NotEnoughData { expected, actual }
115    }
116
117    /// Create a new invalid signature error
118    pub fn invalid_signature<S: Into<String>>(expected: S, actual: S) -> Self {
119        Self::InvalidSignature {
120            expected: expected.into(),
121            actual: actual.into(),
122        }
123    }
124
125    /// Create a new unsupported feature error
126    pub fn unsupported<S: Into<String>>(feature: S) -> Self {
127        Self::Unsupported(feature.into())
128    }
129
130    /// Create a new generic error
131    pub fn generic<S: Into<String>>(msg: S) -> Self {
132        Self::Generic(msg.into())
133    }
134
135    /// Create a new I/O error (alias for generic)
136    pub fn io_error<S: Into<String>>(msg: S) -> Self {
137        Self::Generic(msg.into())
138    }
139}
140
141// Conversion from other error types
142impl From<lz4_flex::block::DecompressError> for BinaryError {
143    fn from(err: lz4_flex::block::DecompressError) -> Self {
144        Self::decompression_failed(format!("LZ4 decompression failed: {}", err))
145    }
146}
147
148impl From<lz4_flex::frame::Error> for BinaryError {
149    fn from(err: lz4_flex::frame::Error) -> Self {
150        Self::decompression_failed(format!("LZ4 frame error: {}", err))
151    }
152}
153
154impl From<std::string::FromUtf8Error> for BinaryError {
155    fn from(err: std::string::FromUtf8Error) -> Self {
156        Self::invalid_data(format!("Invalid UTF-8 string: {}", err))
157    }
158}
159
160impl From<std::str::Utf8Error> for BinaryError {
161    fn from(err: std::str::Utf8Error) -> Self {
162        Self::invalid_data(format!("Invalid UTF-8 string: {}", err))
163    }
164}
165
166/// Error severity levels
167#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
168pub enum ErrorSeverity {
169    /// Low severity - can be ignored
170    Low,
171    /// Medium severity - should be logged
172    Medium,
173    /// High severity - requires attention
174    High,
175    /// Critical severity - operation cannot continue
176    Critical,
177}
178
179impl BinaryError {
180    /// Create a memory error
181    pub fn memory_error(msg: impl Into<String>) -> Self {
182        BinaryError::MemoryError(msg.into())
183    }
184
185    /// Create a timeout error
186    pub fn timeout(msg: impl Into<String>) -> Self {
187        BinaryError::Timeout(msg.into())
188    }
189
190    /// Create a corrupted data error
191    pub fn corrupted_data(msg: impl Into<String>) -> Self {
192        BinaryError::CorruptedData(msg.into())
193    }
194
195    /// Create a version compatibility error
196    pub fn version_compatibility(msg: impl Into<String>) -> Self {
197        BinaryError::VersionCompatibility(msg.into())
198    }
199
200    /// Check if this error is recoverable
201    pub fn is_recoverable(&self) -> bool {
202        match self {
203            BinaryError::Io(_) => false,
204            BinaryError::InvalidFormat(_) => false,
205            BinaryError::UnsupportedVersion(_) => false,
206            BinaryError::UnsupportedCompression(_) => true, // Might try different compression
207            BinaryError::DecompressionFailed(_) => true,    // Might retry or skip
208            BinaryError::InvalidData(_) => true,            // Might skip corrupted object
209            BinaryError::ParseError(_) => true,             // Might skip problematic object
210            BinaryError::NotEnoughData { .. } => false,
211            BinaryError::InvalidSignature { .. } => false,
212            BinaryError::Unsupported(_) => true, // Might skip unsupported feature
213            BinaryError::MemoryError(_) => false,
214            BinaryError::Timeout(_) => true, // Might retry
215            BinaryError::ResourceLimitExceeded(_) => true, // Might reduce limits
216            BinaryError::CorruptedData(_) => true, // Might skip corrupted section
217            BinaryError::VersionCompatibility(_) => true, // Might use compatibility mode
218            BinaryError::Generic(_) => true, // Generic errors are usually recoverable
219        }
220    }
221
222    /// Get error severity level
223    pub fn severity(&self) -> ErrorSeverity {
224        match self {
225            BinaryError::Io(_) => ErrorSeverity::Critical,
226            BinaryError::InvalidFormat(_) => ErrorSeverity::Critical,
227            BinaryError::UnsupportedVersion(_) => ErrorSeverity::High,
228            BinaryError::UnsupportedCompression(_) => ErrorSeverity::Medium,
229            BinaryError::DecompressionFailed(_) => ErrorSeverity::Medium,
230            BinaryError::InvalidData(_) => ErrorSeverity::Medium,
231            BinaryError::ParseError(_) => ErrorSeverity::Medium,
232            BinaryError::NotEnoughData { .. } => ErrorSeverity::High,
233            BinaryError::InvalidSignature { .. } => ErrorSeverity::High,
234            BinaryError::Unsupported(_) => ErrorSeverity::Low,
235            BinaryError::MemoryError(_) => ErrorSeverity::Critical,
236            BinaryError::Timeout(_) => ErrorSeverity::Medium,
237            BinaryError::ResourceLimitExceeded(_) => ErrorSeverity::Medium,
238            BinaryError::CorruptedData(_) => ErrorSeverity::Medium,
239            BinaryError::VersionCompatibility(_) => ErrorSeverity::Low,
240            BinaryError::Generic(_) => ErrorSeverity::Medium,
241        }
242    }
243
244    /// Get suggested recovery action
245    pub fn recovery_suggestion(&self) -> Option<&'static str> {
246        match self {
247            BinaryError::UnsupportedCompression(_) => Some("Try different compression method"),
248            BinaryError::DecompressionFailed(_) => Some("Skip compressed section or retry"),
249            BinaryError::InvalidData(_) => Some("Skip corrupted object and continue"),
250            BinaryError::ParseError(_) => Some("Skip problematic object and continue"),
251            BinaryError::Unsupported(_) => Some("Skip unsupported feature"),
252            BinaryError::Timeout(_) => Some("Retry with longer timeout"),
253            BinaryError::ResourceLimitExceeded(_) => Some("Reduce processing limits"),
254            BinaryError::CorruptedData(_) => Some("Skip corrupted section"),
255            BinaryError::VersionCompatibility(_) => Some("Enable compatibility mode"),
256            _ => None,
257        }
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn test_error_creation() {
267        let err = BinaryError::invalid_format("test format");
268        assert!(matches!(err, BinaryError::InvalidFormat(_)));
269        assert_eq!(err.to_string(), "Invalid file format: test format");
270    }
271
272    #[test]
273    fn test_not_enough_data_error() {
274        let err = BinaryError::not_enough_data(100, 50);
275        assert!(matches!(err, BinaryError::NotEnoughData { .. }));
276        assert_eq!(err.to_string(), "Not enough data: expected 100, got 50");
277    }
278
279    #[test]
280    fn test_invalid_signature_error() {
281        let err = BinaryError::invalid_signature("UnityFS", "UnityWeb");
282        assert!(matches!(err, BinaryError::InvalidSignature { .. }));
283        assert_eq!(
284            err.to_string(),
285            "Invalid signature: expected UnityFS, got UnityWeb"
286        );
287    }
288}