1use std::error::Error;
5use std::fmt;
6
7use zip::result::ZipError;
8
9#[derive(Debug)]
10pub enum RomAnalyzerError {
11 UnsupportedFormat(String),
13 DataTooSmall {
15 file_size: usize,
16 required_size: usize,
17 details: String,
18 },
19 InvalidHeader(String),
21 ParsingError(String),
23 ChecksumMismatch(String),
25 ArchiveError(String),
27 IoError(std::io::Error),
29 ZipError(ZipError),
31 ChdError(chd::Error),
33 FileNotFound(String),
35 Generic(String),
37 WithPath(String, Box<RomAnalyzerError>),
39}
40
41impl RomAnalyzerError {
42 pub fn new(msg: &str) -> RomAnalyzerError {
52 RomAnalyzerError::Generic(msg.to_string())
53 }
54}
55
56impl fmt::Display for RomAnalyzerError {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 match self {
59 RomAnalyzerError::UnsupportedFormat(msg) => write!(f, "Unsupported format: {}", msg),
60 RomAnalyzerError::DataTooSmall {
61 file_size,
62 required_size,
63 details,
64 } => write!(
65 f,
66 "ROM data too small: {} bytes, requires at least {} bytes. {}",
67 file_size, required_size, details
68 ),
69 RomAnalyzerError::InvalidHeader(msg) => write!(f, "Invalid header: {}", msg),
70 RomAnalyzerError::ParsingError(msg) => write!(f, "Parsing error: {}", msg),
71 RomAnalyzerError::ChecksumMismatch(msg) => write!(f, "Checksum mismatch: {}", msg),
72 RomAnalyzerError::ArchiveError(msg) => write!(f, "Archive error: {}", msg),
73 RomAnalyzerError::IoError(err) => write!(f, "IO error: {}", err),
74 RomAnalyzerError::ZipError(err) => write!(f, "ZIP error: {}", err),
75 RomAnalyzerError::ChdError(err) => write!(f, "CHD error: {}", err),
76 RomAnalyzerError::FileNotFound(path) => write!(f, "File not found: {}", path),
77 RomAnalyzerError::Generic(msg) => write!(f, "{}", msg),
78 RomAnalyzerError::WithPath(path, err) => {
79 write!(f, "Error processing file {}: {}", path, err)
80 }
81 }
82 }
83}
84
85impl Error for RomAnalyzerError {
86 fn source(&self) -> Option<&(dyn Error + 'static)> {
87 match self {
88 RomAnalyzerError::IoError(err) => Some(err),
89 RomAnalyzerError::ZipError(err) => Some(err),
90 RomAnalyzerError::ChdError(err) => Some(err),
91 RomAnalyzerError::WithPath(_, err) => err.source(),
92 _ => None,
93 }
94 }
95}
96
97impl From<ZipError> for RomAnalyzerError {
99 fn from(err: ZipError) -> RomAnalyzerError {
100 RomAnalyzerError::ZipError(err)
101 }
102}
103
104impl From<std::io::Error> for RomAnalyzerError {
106 fn from(err: std::io::Error) -> RomAnalyzerError {
107 RomAnalyzerError::IoError(err)
108 }
109}
110
111impl From<Box<dyn Error>> for RomAnalyzerError {
113 fn from(err: Box<dyn Error>) -> RomAnalyzerError {
114 RomAnalyzerError::Generic(err.to_string())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use std::io::{Error as IoError, ErrorKind};
122
123 #[test]
124 fn test_new_error() {
125 let error_msg = "Test error message";
126 let err = RomAnalyzerError::new(error_msg);
127 match err {
128 RomAnalyzerError::Generic(msg) => assert_eq!(msg, error_msg),
129 _ => panic!("Expected Generic variant"),
130 }
131 }
132
133 #[test]
134 fn test_display_trait() {
135 let error_msg = "Display test";
136 let err = RomAnalyzerError::Generic(error_msg.to_string());
137 assert_eq!(format!("{}", err), error_msg);
138 }
139
140 #[test]
141 fn test_display_unsupported_format() {
142 let err = RomAnalyzerError::UnsupportedFormat("test.ext".to_string());
143 assert_eq!(format!("{}", err), "Unsupported format: test.ext");
144 }
145
146 #[test]
147 fn test_display_data_too_small() {
148 let err = RomAnalyzerError::DataTooSmall {
149 file_size: 100,
150 required_size: 200,
151 details: "Header missing".to_string(),
152 };
153 assert_eq!(
154 format!("{}", err),
155 "ROM data too small: 100 bytes, requires at least 200 bytes. Header missing"
156 );
157 }
158
159 #[test]
160 fn test_display_file_not_found() {
161 let err = RomAnalyzerError::FileNotFound("test.nes".to_string());
162 assert_eq!(format!("{}", err), "File not found: test.nes");
163 }
164
165 #[test]
166 fn test_from_zip_error() {
167 let zip_err = ZipError::FileNotFound;
168 let zip_err_display = format!("{}", zip_err);
169 let err: RomAnalyzerError = zip_err.into();
170 match err {
171 RomAnalyzerError::ZipError(_) => assert_eq!(
172 format!("{}", err),
173 format!("ZIP error: {}", zip_err_display)
174 ),
175 _ => panic!("Expected ZipError variant"),
176 }
177 }
178
179 #[test]
180 fn test_from_io_error() {
181 let io_err = IoError::new(ErrorKind::NotFound, "File not found");
182 let err: RomAnalyzerError = io_err.into();
183 match err {
184 RomAnalyzerError::IoError(_) => assert!(format!("{}", err).contains("IO error")),
185 _ => panic!("Expected IoError variant"),
186 }
187 }
188
189 #[test]
190 fn test_error_source_method() {
191 let io_err = IoError::new(ErrorKind::NotFound, "File not found");
193 let rom_err = RomAnalyzerError::IoError(io_err);
194 assert!(rom_err.source().is_some());
195 assert_eq!(rom_err.source().unwrap().to_string(), "File not found");
196
197 let zip_err = ZipError::FileNotFound;
199 let rom_err = RomAnalyzerError::ZipError(zip_err);
200 assert!(rom_err.source().is_some());
201
202 let rom_err = RomAnalyzerError::Generic("test".to_string());
204 assert!(rom_err.source().is_none());
205
206 let rom_err = RomAnalyzerError::UnsupportedFormat("test".to_string());
207 assert!(rom_err.source().is_none());
208
209 let rom_err = RomAnalyzerError::DataTooSmall {
210 file_size: 100,
211 required_size: 200,
212 details: "test".to_string(),
213 };
214 assert!(rom_err.source().is_none());
215
216 let rom_err = RomAnalyzerError::InvalidHeader("test".to_string());
217 assert!(rom_err.source().is_none());
218
219 let rom_err = RomAnalyzerError::ParsingError("test".to_string());
220 assert!(rom_err.source().is_none());
221
222 let rom_err = RomAnalyzerError::FileNotFound("test".to_string());
223 assert!(rom_err.source().is_none());
224 }
225
226 #[test]
227 fn test_error_source_chd_error() {
228 use tempfile::tempdir;
230
231 let dir = tempdir().unwrap();
232 let chd_path = dir.path().join("test.chd");
233 std::fs::write(&chd_path, b"invalid chd data").unwrap();
234
235 let result = crate::archive::chd::analyze_chd_file(&chd_path);
237 assert!(result.is_err());
238
239 if let Err(RomAnalyzerError::ChdError(chd_err)) = result {
240 let rom_err = RomAnalyzerError::ChdError(chd_err);
242 assert!(rom_err.source().is_some(), "ChdError should have a source");
243 } else {
244 panic!("Expected ChdError, but got {:?}", result.unwrap_err());
245 }
246 }
247
248 #[test]
249 fn test_error_source_with_path() {
250 let io_err = IoError::new(ErrorKind::NotFound, "File not found");
252 let inner_err = RomAnalyzerError::IoError(io_err);
253 let wrapped_err = RomAnalyzerError::WithPath("test.nes".to_string(), Box::new(inner_err));
254 assert!(wrapped_err.source().is_some());
255 assert_eq!(wrapped_err.source().unwrap().to_string(), "File not found");
256
257 let inner_err_no_source = RomAnalyzerError::Generic("test".to_string());
259 let wrapped_err_no_source =
260 RomAnalyzerError::WithPath("test.nes".to_string(), Box::new(inner_err_no_source));
261 assert!(wrapped_err_no_source.source().is_none());
262 }
263}