1use alloc::format;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::fmt;
7
8pub type Result<T> = core::result::Result<T, Error>;
10
11pub fn parse_error(input: &str, offset: usize, context: impl Into<String>) -> Error {
20 let snippet = input.chars().take(50).collect::<String>();
21 Error::ParseError {
22 input: snippet,
23 offset,
24 context: context.into(),
25 }
26}
27
28#[derive(Debug)]
30pub enum Error {
31 InvalidMagic {
36 found: u32,
38 expected: &'static [u32],
40 },
41
42 UnknownType {
44 type_byte: u8,
46 offset: usize,
48 },
49
50 InvalidStringIndex {
52 index: usize,
54 max: usize,
56 },
57
58 UnexpectedEndOfInput {
60 context: &'static str,
62 offset: usize,
64 expected: usize,
66 actual: usize,
68 },
69
70 InvalidUtf8 {
72 offset: usize,
74 },
75
76 InvalidUtf16 {
78 offset: usize,
80 position: usize,
82 },
83
84 ParseError {
89 input: String,
91 offset: usize,
93 context: String,
95 },
96}
97
98impl fmt::Display for Error {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 Error::InvalidMagic { found, expected } => {
102 let expected_str: String = expected
103 .iter()
104 .map(|v| format!("0x{:08x}", v))
105 .collect::<Vec<_>>()
106 .join(" or ");
107 write!(
108 f,
109 "Invalid magic number: expected {}, found 0x{:08x}",
110 expected_str, found
111 )
112 }
113 Error::UnknownType { type_byte, offset } => {
114 write!(
115 f,
116 "Unknown type byte 0x{:02x} at offset {}",
117 type_byte, offset
118 )
119 }
120 Error::InvalidStringIndex { index, max } => {
121 if *max == 0 {
122 write!(f, "Invalid string index {}: string table is empty", index)
123 } else {
124 write!(
125 f,
126 "Invalid string index {}: string table has {} entries (valid range: 0..{})",
127 index, max, max
128 )
129 }
130 }
131 Error::UnexpectedEndOfInput {
132 context,
133 offset,
134 expected,
135 actual,
136 } => {
137 write!(
138 f,
139 "Unexpected end of input at offset {} while {}: expected {} bytes, found {}",
140 offset, context, expected, actual
141 )
142 }
143 Error::InvalidUtf8 { offset } => {
144 write!(f, "Invalid UTF-8 sequence at offset {}", offset)
145 }
146 Error::InvalidUtf16 { offset, position } => {
147 write!(
148 f,
149 "Invalid UTF-16 sequence at offset {} (surrogate position {})",
150 offset, position
151 )
152 }
153 Error::ParseError {
154 input,
155 offset,
156 context,
157 } => {
158 let snippet = if input.len() > 50 {
159 format!("{}...", &input[..50])
160 } else {
161 input.clone()
162 };
163 write!(
164 f,
165 "Parse error at offset {}: {} (near: \"{}\")",
166 offset, context, snippet
167 )
168 }
169 }
170 }
171}
172
173impl core::error::Error for Error {}
174
175impl Error {
176 fn with_offset(mut self, base: usize) -> Self {
181 match &mut self {
182 Error::UnexpectedEndOfInput { offset, .. } => *offset += base,
183 Error::InvalidUtf8 { offset } => *offset += base,
184 Error::InvalidUtf16 { offset, .. } => *offset += base,
185 Error::UnknownType { offset, .. } => *offset += base,
186 Error::ParseError { offset, .. } => *offset += base,
187 Error::InvalidMagic { .. } | Error::InvalidStringIndex { .. } => {}
189 }
190 self
191 }
192}
193
194pub(crate) fn with_offset(base: usize) -> impl Fn(Error) -> Error {
198 move |err| err.with_offset(base)
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::binary::{APPINFO_MAGIC_40, APPINFO_MAGIC_41};
205 use alloc::format;
206 use alloc::string::ToString;
207
208 #[test]
209 fn test_error_display_invalid_magic() {
210 let err = Error::InvalidMagic {
211 found: 0xDEADBEEF,
212 expected: &[APPINFO_MAGIC_40, APPINFO_MAGIC_41],
213 };
214 let msg = format!("{}", err);
215 assert!(msg.contains("0xdeadbeef"));
216 assert!(msg.contains("0x07564428"));
217 }
218
219 #[test]
220 fn test_error_display_unknown_type() {
221 let err = Error::UnknownType {
222 type_byte: 0xFF,
223 offset: 42,
224 };
225 let msg = format!("{}", err);
226 assert!(msg.contains("0xff"));
227 assert!(msg.contains("42"));
228 }
229
230 #[test]
231 fn test_error_display_invalid_string_index() {
232 let err = Error::InvalidStringIndex { index: 5, max: 3 };
233 let msg = format!("{}", err);
234 assert!(msg.contains("5"));
235 assert!(msg.contains("3"));
236 }
237
238 #[test]
239 fn test_error_display_invalid_string_index_empty() {
240 let err = Error::InvalidStringIndex { index: 0, max: 0 };
241 let msg = format!("{}", err);
242 assert!(msg.contains("empty"));
243 }
244
245 #[test]
246 fn test_error_display_unexpected_end_of_input() {
247 let err = Error::UnexpectedEndOfInput {
248 context: "reading string",
249 offset: 10,
250 expected: 4,
251 actual: 2,
252 };
253 let msg = format!("{}", err);
254 assert!(msg.contains("reading string"));
255 assert!(msg.contains("10"));
256 assert!(msg.contains("expected 4"));
257 assert!(msg.contains("found 2"));
258 }
259
260 #[test]
261 fn test_error_display_invalid_utf8() {
262 let err = Error::InvalidUtf8 { offset: 15 };
263 let msg = format!("{}", err);
264 assert!(msg.contains("15"));
265 }
266
267 #[test]
268 fn test_error_display_invalid_utf16() {
269 let err = Error::InvalidUtf16 {
270 offset: 20,
271 position: 3,
272 };
273 let msg = format!("{}", err);
274 assert!(msg.contains("20"));
275 assert!(msg.contains("3"));
276 }
277
278 #[test]
279 fn test_error_display_parse_error() {
280 let err = Error::ParseError {
281 input: "some very long input that should be truncated in the message".to_string(),
282 offset: 5,
283 context: "expected quote".to_string(),
284 };
285 let msg = format!("{}", err);
286 assert!(msg.contains("5"));
287 assert!(msg.contains("expected quote"));
288 assert!(msg.len() < 150);
290 }
291
292 #[test]
293 fn test_error_with_offset_unexpected_end() {
294 let err = Error::UnexpectedEndOfInput {
295 context: "test",
296 offset: 10,
297 expected: 4,
298 actual: 2,
299 };
300 let adjusted = err.with_offset(100);
301 match adjusted {
302 Error::UnexpectedEndOfInput { offset, .. } => {
303 assert_eq!(offset, 110);
304 }
305 _ => panic!("Unexpected error type"),
306 }
307 }
308
309 #[test]
310 fn test_error_with_offset_invalid_utf8() {
311 let err = Error::InvalidUtf8 { offset: 5 };
312 let adjusted = err.with_offset(100);
313 match adjusted {
314 Error::InvalidUtf8 { offset } => {
315 assert_eq!(offset, 105);
316 }
317 _ => panic!("Unexpected error type"),
318 }
319 }
320
321 #[test]
322 fn test_error_with_offset_invalid_utf16() {
323 let err = Error::InvalidUtf16 {
324 offset: 10,
325 position: 2,
326 };
327 let adjusted = err.with_offset(100);
328 match adjusted {
329 Error::InvalidUtf16 { offset, position } => {
330 assert_eq!(offset, 110);
331 assert_eq!(position, 2);
332 }
333 _ => panic!("Unexpected error type"),
334 }
335 }
336
337 #[test]
338 fn test_error_with_offset_unknown_type() {
339 let err = Error::UnknownType {
340 type_byte: 0x42,
341 offset: 7,
342 };
343 let adjusted = err.with_offset(100);
344 match adjusted {
345 Error::UnknownType { type_byte, offset } => {
346 assert_eq!(type_byte, 0x42);
347 assert_eq!(offset, 107);
348 }
349 _ => panic!("Unexpected error type"),
350 }
351 }
352
353 #[test]
354 fn test_error_with_offset_parse_error() {
355 let err = Error::ParseError {
356 input: "test".to_string(),
357 offset: 3,
358 context: "context".to_string(),
359 };
360 let adjusted = err.with_offset(100);
361 match adjusted {
362 Error::ParseError { offset, .. } => {
363 assert_eq!(offset, 103);
364 }
365 _ => panic!("Unexpected error type"),
366 }
367 }
368
369 #[test]
370 fn test_error_with_offset_no_change_for_non_offset_variants() {
371 let err = Error::InvalidMagic {
372 found: 0x12345678,
373 expected: &[APPINFO_MAGIC_40],
374 };
375 let adjusted = err.with_offset(100);
376 match adjusted {
378 Error::InvalidMagic { found, .. } => {
379 assert_eq!(found, 0x12345678);
380 }
381 _ => panic!("Unexpected error type"),
382 }
383 }
384
385 #[test]
386 fn test_parse_error_truncates_long_input() {
387 let long_input = "a".repeat(100);
388 let err = parse_error(&long_input, 0, "test context");
389 match err {
390 Error::ParseError { input, .. } => {
391 assert!(input.len() <= 50, "Input should be truncated to 50 chars");
392 }
393 _ => panic!("Expected ParseError variant"),
394 }
395 }
396
397 #[test]
398 fn test_with_offset_closure() {
399 let base_offset = 100;
400 let f = with_offset(base_offset);
401 let err = Error::InvalidUtf8 { offset: 5 };
402 let adjusted = f(err);
403 match adjusted {
404 Error::InvalidUtf8 { offset } => {
405 assert_eq!(offset, 105);
406 }
407 _ => panic!("Unexpected error type"),
408 }
409 }
410}