use std::error::Error;
use std::fmt;
use zip::result::ZipError;
#[derive(Debug)]
pub enum RomAnalyzerError {
UnsupportedFormat(String),
DataTooSmall {
file_size: usize,
required_size: usize,
details: String,
},
InvalidHeader(String),
ParsingError(String),
ChecksumMismatch(String),
ArchiveError(String),
IoError(std::io::Error),
ZipError(ZipError),
ChdError(chd::Error),
FileNotFound(String),
Generic(String),
WithPath(String, Box<RomAnalyzerError>),
}
impl RomAnalyzerError {
pub fn new(msg: &str) -> RomAnalyzerError {
RomAnalyzerError::Generic(msg.to_string())
}
}
impl fmt::Display for RomAnalyzerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RomAnalyzerError::UnsupportedFormat(msg) => write!(f, "Unsupported format: {}", msg),
RomAnalyzerError::DataTooSmall {
file_size,
required_size,
details,
} => write!(
f,
"ROM data too small: {} bytes, requires at least {} bytes. {}",
file_size, required_size, details
),
RomAnalyzerError::InvalidHeader(msg) => write!(f, "Invalid header: {}", msg),
RomAnalyzerError::ParsingError(msg) => write!(f, "Parsing error: {}", msg),
RomAnalyzerError::ChecksumMismatch(msg) => write!(f, "Checksum mismatch: {}", msg),
RomAnalyzerError::ArchiveError(msg) => write!(f, "Archive error: {}", msg),
RomAnalyzerError::IoError(err) => write!(f, "IO error: {}", err),
RomAnalyzerError::ZipError(err) => write!(f, "ZIP error: {}", err),
RomAnalyzerError::ChdError(err) => write!(f, "CHD error: {}", err),
RomAnalyzerError::FileNotFound(path) => write!(f, "File not found: {}", path),
RomAnalyzerError::Generic(msg) => write!(f, "{}", msg),
RomAnalyzerError::WithPath(path, err) => {
write!(f, "Error processing file {}: {}", path, err)
}
}
}
}
impl Error for RomAnalyzerError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
RomAnalyzerError::IoError(err) => Some(err),
RomAnalyzerError::ZipError(err) => Some(err),
RomAnalyzerError::ChdError(err) => Some(err),
RomAnalyzerError::WithPath(_, err) => err.source(),
_ => None,
}
}
}
impl From<ZipError> for RomAnalyzerError {
fn from(err: ZipError) -> RomAnalyzerError {
RomAnalyzerError::ZipError(err)
}
}
impl From<std::io::Error> for RomAnalyzerError {
fn from(err: std::io::Error) -> RomAnalyzerError {
RomAnalyzerError::IoError(err)
}
}
impl From<Box<dyn Error>> for RomAnalyzerError {
fn from(err: Box<dyn Error>) -> RomAnalyzerError {
RomAnalyzerError::Generic(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn test_new_error() {
let error_msg = "Test error message";
let err = RomAnalyzerError::new(error_msg);
match err {
RomAnalyzerError::Generic(msg) => assert_eq!(msg, error_msg),
_ => panic!("Expected Generic variant"),
}
}
#[test]
fn test_display_trait() {
let error_msg = "Display test";
let err = RomAnalyzerError::Generic(error_msg.to_string());
assert_eq!(format!("{}", err), error_msg);
}
#[test]
fn test_display_unsupported_format() {
let err = RomAnalyzerError::UnsupportedFormat("test.ext".to_string());
assert_eq!(format!("{}", err), "Unsupported format: test.ext");
}
#[test]
fn test_display_data_too_small() {
let err = RomAnalyzerError::DataTooSmall {
file_size: 100,
required_size: 200,
details: "Header missing".to_string(),
};
assert_eq!(
format!("{}", err),
"ROM data too small: 100 bytes, requires at least 200 bytes. Header missing"
);
}
#[test]
fn test_display_file_not_found() {
let err = RomAnalyzerError::FileNotFound("test.nes".to_string());
assert_eq!(format!("{}", err), "File not found: test.nes");
}
#[test]
fn test_from_zip_error() {
let zip_err = ZipError::FileNotFound;
let zip_err_display = format!("{}", zip_err);
let err: RomAnalyzerError = zip_err.into();
match err {
RomAnalyzerError::ZipError(_) => assert_eq!(
format!("{}", err),
format!("ZIP error: {}", zip_err_display)
),
_ => panic!("Expected ZipError variant"),
}
}
#[test]
fn test_from_io_error() {
let io_err = IoError::new(ErrorKind::NotFound, "File not found");
let err: RomAnalyzerError = io_err.into();
match err {
RomAnalyzerError::IoError(_) => assert!(format!("{}", err).contains("IO error")),
_ => panic!("Expected IoError variant"),
}
}
#[test]
fn test_error_source_method() {
let io_err = IoError::new(ErrorKind::NotFound, "File not found");
let rom_err = RomAnalyzerError::IoError(io_err);
assert!(rom_err.source().is_some());
assert_eq!(rom_err.source().unwrap().to_string(), "File not found");
let zip_err = ZipError::FileNotFound;
let rom_err = RomAnalyzerError::ZipError(zip_err);
assert!(rom_err.source().is_some());
let rom_err = RomAnalyzerError::Generic("test".to_string());
assert!(rom_err.source().is_none());
let rom_err = RomAnalyzerError::UnsupportedFormat("test".to_string());
assert!(rom_err.source().is_none());
let rom_err = RomAnalyzerError::DataTooSmall {
file_size: 100,
required_size: 200,
details: "test".to_string(),
};
assert!(rom_err.source().is_none());
let rom_err = RomAnalyzerError::InvalidHeader("test".to_string());
assert!(rom_err.source().is_none());
let rom_err = RomAnalyzerError::ParsingError("test".to_string());
assert!(rom_err.source().is_none());
let rom_err = RomAnalyzerError::FileNotFound("test".to_string());
assert!(rom_err.source().is_none());
}
#[test]
fn test_error_source_chd_error() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let chd_path = dir.path().join("test.chd");
std::fs::write(&chd_path, b"invalid chd data").unwrap();
let result = crate::archive::chd::analyze_chd_file(&chd_path);
assert!(result.is_err());
if let Err(RomAnalyzerError::ChdError(chd_err)) = result {
let rom_err = RomAnalyzerError::ChdError(chd_err);
assert!(rom_err.source().is_some(), "ChdError should have a source");
} else {
panic!("Expected ChdError, but got {:?}", result.unwrap_err());
}
}
#[test]
fn test_error_source_with_path() {
let io_err = IoError::new(ErrorKind::NotFound, "File not found");
let inner_err = RomAnalyzerError::IoError(io_err);
let wrapped_err = RomAnalyzerError::WithPath("test.nes".to_string(), Box::new(inner_err));
assert!(wrapped_err.source().is_some());
assert_eq!(wrapped_err.source().unwrap().to_string(), "File not found");
let inner_err_no_source = RomAnalyzerError::Generic("test".to_string());
let wrapped_err_no_source =
RomAnalyzerError::WithPath("test.nes".to_string(), Box::new(inner_err_no_source));
assert!(wrapped_err_no_source.source().is_none());
}
}