use thiserror::Error;
pub type Result<T> = std::result::Result<T, BinaryError>;
#[derive(Error, Debug)]
pub enum BinaryError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid file format: {0}")]
InvalidFormat(String),
#[error("Unsupported file version: {0}")]
UnsupportedVersion(String),
#[error("Unsupported compression: {0}")]
UnsupportedCompression(String),
#[error("Decompression failed: {0}")]
DecompressionFailed(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Not enough data: expected {expected}, got {actual}")]
NotEnoughData { expected: usize, actual: usize },
#[error("Invalid signature: expected {expected}, got {actual}")]
InvalidSignature { expected: String, actual: String },
#[error("Unsupported feature: {0}")]
Unsupported(String),
#[error("Memory allocation error: {0}")]
MemoryError(String),
#[error("Operation timed out: {0}")]
Timeout(String),
#[error("Resource limit exceeded: {0}")]
ResourceLimitExceeded(String),
#[error("Corrupted data detected: {0}")]
CorruptedData(String),
#[error("Version compatibility error: {0}")]
VersionCompatibility(String),
#[error("Error: {0}")]
Generic(String),
}
impl BinaryError {
pub fn invalid_format<S: Into<String>>(msg: S) -> Self {
Self::InvalidFormat(msg.into())
}
pub fn format<S: Into<String>>(msg: S) -> Self {
Self::Generic(msg.into())
}
pub fn unsupported_version<S: Into<String>>(version: S) -> Self {
Self::UnsupportedVersion(version.into())
}
pub fn unsupported_compression<S: Into<String>>(compression: S) -> Self {
Self::UnsupportedCompression(compression.into())
}
pub fn decompression_failed<S: Into<String>>(msg: S) -> Self {
Self::DecompressionFailed(msg.into())
}
pub fn invalid_data<S: Into<String>>(msg: S) -> Self {
Self::InvalidData(msg.into())
}
pub fn parse_error<S: Into<String>>(msg: S) -> Self {
Self::ParseError(msg.into())
}
pub fn not_enough_data(expected: usize, actual: usize) -> Self {
Self::NotEnoughData { expected, actual }
}
pub fn invalid_signature<S: Into<String>>(expected: S, actual: S) -> Self {
Self::InvalidSignature {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn unsupported<S: Into<String>>(feature: S) -> Self {
Self::Unsupported(feature.into())
}
pub fn generic<S: Into<String>>(msg: S) -> Self {
Self::Generic(msg.into())
}
pub fn io_error<S: Into<String>>(msg: S) -> Self {
Self::Generic(msg.into())
}
}
impl From<lz4_flex::block::DecompressError> for BinaryError {
fn from(err: lz4_flex::block::DecompressError) -> Self {
Self::decompression_failed(format!("LZ4 decompression failed: {}", err))
}
}
impl From<lz4_flex::frame::Error> for BinaryError {
fn from(err: lz4_flex::frame::Error) -> Self {
Self::decompression_failed(format!("LZ4 frame error: {}", err))
}
}
impl From<std::string::FromUtf8Error> for BinaryError {
fn from(err: std::string::FromUtf8Error) -> Self {
Self::invalid_data(format!("Invalid UTF-8 string: {}", err))
}
}
impl From<std::str::Utf8Error> for BinaryError {
fn from(err: std::str::Utf8Error) -> Self {
Self::invalid_data(format!("Invalid UTF-8 string: {}", err))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorSeverity {
Low,
Medium,
High,
Critical,
}
impl BinaryError {
pub fn memory_error(msg: impl Into<String>) -> Self {
BinaryError::MemoryError(msg.into())
}
pub fn timeout(msg: impl Into<String>) -> Self {
BinaryError::Timeout(msg.into())
}
pub fn corrupted_data(msg: impl Into<String>) -> Self {
BinaryError::CorruptedData(msg.into())
}
pub fn version_compatibility(msg: impl Into<String>) -> Self {
BinaryError::VersionCompatibility(msg.into())
}
pub fn is_recoverable(&self) -> bool {
match self {
BinaryError::Io(_) => false,
BinaryError::InvalidFormat(_) => false,
BinaryError::UnsupportedVersion(_) => false,
BinaryError::UnsupportedCompression(_) => true, BinaryError::DecompressionFailed(_) => true, BinaryError::InvalidData(_) => true, BinaryError::ParseError(_) => true, BinaryError::NotEnoughData { .. } => false,
BinaryError::InvalidSignature { .. } => false,
BinaryError::Unsupported(_) => true, BinaryError::MemoryError(_) => false,
BinaryError::Timeout(_) => true, BinaryError::ResourceLimitExceeded(_) => true, BinaryError::CorruptedData(_) => true, BinaryError::VersionCompatibility(_) => true, BinaryError::Generic(_) => true, }
}
pub fn severity(&self) -> ErrorSeverity {
match self {
BinaryError::Io(_) => ErrorSeverity::Critical,
BinaryError::InvalidFormat(_) => ErrorSeverity::Critical,
BinaryError::UnsupportedVersion(_) => ErrorSeverity::High,
BinaryError::UnsupportedCompression(_) => ErrorSeverity::Medium,
BinaryError::DecompressionFailed(_) => ErrorSeverity::Medium,
BinaryError::InvalidData(_) => ErrorSeverity::Medium,
BinaryError::ParseError(_) => ErrorSeverity::Medium,
BinaryError::NotEnoughData { .. } => ErrorSeverity::High,
BinaryError::InvalidSignature { .. } => ErrorSeverity::High,
BinaryError::Unsupported(_) => ErrorSeverity::Low,
BinaryError::MemoryError(_) => ErrorSeverity::Critical,
BinaryError::Timeout(_) => ErrorSeverity::Medium,
BinaryError::ResourceLimitExceeded(_) => ErrorSeverity::Medium,
BinaryError::CorruptedData(_) => ErrorSeverity::Medium,
BinaryError::VersionCompatibility(_) => ErrorSeverity::Low,
BinaryError::Generic(_) => ErrorSeverity::Medium,
}
}
pub fn recovery_suggestion(&self) -> Option<&'static str> {
match self {
BinaryError::UnsupportedCompression(_) => Some("Try different compression method"),
BinaryError::DecompressionFailed(_) => Some("Skip compressed section or retry"),
BinaryError::InvalidData(_) => Some("Skip corrupted object and continue"),
BinaryError::ParseError(_) => Some("Skip problematic object and continue"),
BinaryError::Unsupported(_) => Some("Skip unsupported feature"),
BinaryError::Timeout(_) => Some("Retry with longer timeout"),
BinaryError::ResourceLimitExceeded(_) => Some("Reduce processing limits"),
BinaryError::CorruptedData(_) => Some("Skip corrupted section"),
BinaryError::VersionCompatibility(_) => Some("Enable compatibility mode"),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = BinaryError::invalid_format("test format");
assert!(matches!(err, BinaryError::InvalidFormat(_)));
assert_eq!(err.to_string(), "Invalid file format: test format");
}
#[test]
fn test_not_enough_data_error() {
let err = BinaryError::not_enough_data(100, 50);
assert!(matches!(err, BinaryError::NotEnoughData { .. }));
assert_eq!(err.to_string(), "Not enough data: expected 100, got 50");
}
#[test]
fn test_invalid_signature_error() {
let err = BinaryError::invalid_signature("UnityFS", "UnityWeb");
assert!(matches!(err, BinaryError::InvalidSignature { .. }));
assert_eq!(
err.to_string(),
"Invalid signature: expected UnityFS, got UnityWeb"
);
}
}