use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
pub type Result<T> = core::result::Result<T, Error>;
pub fn parse_error(input: &str, offset: usize, context: impl Into<String>) -> Error {
let snippet = input.chars().take(50).collect::<String>();
Error::ParseError {
input: snippet,
offset,
context: context.into(),
}
}
#[derive(Debug)]
pub enum Error {
InvalidMagic {
found: u32,
expected: &'static [u32],
},
UnknownType {
type_byte: u8,
offset: usize,
},
InvalidStringIndex {
index: usize,
max: usize,
},
UnexpectedEndOfInput {
context: &'static str,
offset: usize,
expected: usize,
actual: usize,
},
InvalidUtf8 {
offset: usize,
},
InvalidUtf16 {
offset: usize,
position: usize,
},
ParseError {
input: String,
offset: usize,
context: String,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidMagic { found, expected } => {
let expected_str: String = expected
.iter()
.map(|v| format!("0x{:08x}", v))
.collect::<Vec<_>>()
.join(" or ");
write!(
f,
"Invalid magic number: expected {}, found 0x{:08x}",
expected_str, found
)
}
Error::UnknownType { type_byte, offset } => {
write!(
f,
"Unknown type byte 0x{:02x} at offset {}",
type_byte, offset
)
}
Error::InvalidStringIndex { index, max } => {
if *max == 0 {
write!(f, "Invalid string index {}: string table is empty", index)
} else {
write!(
f,
"Invalid string index {}: string table has {} entries (valid range: 0..{})",
index, max, max
)
}
}
Error::UnexpectedEndOfInput {
context,
offset,
expected,
actual,
} => {
write!(
f,
"Unexpected end of input at offset {} while {}: expected {} bytes, found {}",
offset, context, expected, actual
)
}
Error::InvalidUtf8 { offset } => {
write!(f, "Invalid UTF-8 sequence at offset {}", offset)
}
Error::InvalidUtf16 { offset, position } => {
write!(
f,
"Invalid UTF-16 sequence at offset {} (surrogate position {})",
offset, position
)
}
Error::ParseError {
input,
offset,
context,
} => {
let snippet = if input.len() > 50 {
format!("{}...", &input[..50])
} else {
input.clone()
};
write!(
f,
"Parse error at offset {}: {} (near: \"{}\")",
offset, context, snippet
)
}
}
}
}
impl core::error::Error for Error {}
impl Error {
fn with_offset(mut self, base: usize) -> Self {
match &mut self {
Error::UnexpectedEndOfInput { offset, .. } => *offset += base,
Error::InvalidUtf8 { offset } => *offset += base,
Error::InvalidUtf16 { offset, .. } => *offset += base,
Error::UnknownType { offset, .. } => *offset += base,
Error::ParseError { offset, .. } => *offset += base,
Error::InvalidMagic { .. } | Error::InvalidStringIndex { .. } => {}
}
self
}
}
pub(crate) fn with_offset(base: usize) -> impl Fn(Error) -> Error {
move |err| err.with_offset(base)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binary::{APPINFO_MAGIC_40, APPINFO_MAGIC_41};
use alloc::format;
use alloc::string::ToString;
#[test]
fn test_error_display_invalid_magic() {
let err = Error::InvalidMagic {
found: 0xDEADBEEF,
expected: &[APPINFO_MAGIC_40, APPINFO_MAGIC_41],
};
let msg = format!("{}", err);
assert!(msg.contains("0xdeadbeef"));
assert!(msg.contains("0x07564428"));
}
#[test]
fn test_error_display_unknown_type() {
let err = Error::UnknownType {
type_byte: 0xFF,
offset: 42,
};
let msg = format!("{}", err);
assert!(msg.contains("0xff"));
assert!(msg.contains("42"));
}
#[test]
fn test_error_display_invalid_string_index() {
let err = Error::InvalidStringIndex { index: 5, max: 3 };
let msg = format!("{}", err);
assert!(msg.contains("5"));
assert!(msg.contains("3"));
}
#[test]
fn test_error_display_invalid_string_index_empty() {
let err = Error::InvalidStringIndex { index: 0, max: 0 };
let msg = format!("{}", err);
assert!(msg.contains("empty"));
}
#[test]
fn test_error_display_unexpected_end_of_input() {
let err = Error::UnexpectedEndOfInput {
context: "reading string",
offset: 10,
expected: 4,
actual: 2,
};
let msg = format!("{}", err);
assert!(msg.contains("reading string"));
assert!(msg.contains("10"));
assert!(msg.contains("expected 4"));
assert!(msg.contains("found 2"));
}
#[test]
fn test_error_display_invalid_utf8() {
let err = Error::InvalidUtf8 { offset: 15 };
let msg = format!("{}", err);
assert!(msg.contains("15"));
}
#[test]
fn test_error_display_invalid_utf16() {
let err = Error::InvalidUtf16 {
offset: 20,
position: 3,
};
let msg = format!("{}", err);
assert!(msg.contains("20"));
assert!(msg.contains("3"));
}
#[test]
fn test_error_display_parse_error() {
let err = Error::ParseError {
input: "some very long input that should be truncated in the message".to_string(),
offset: 5,
context: "expected quote".to_string(),
};
let msg = format!("{}", err);
assert!(msg.contains("5"));
assert!(msg.contains("expected quote"));
assert!(msg.len() < 150);
}
#[test]
fn test_error_with_offset_unexpected_end() {
let err = Error::UnexpectedEndOfInput {
context: "test",
offset: 10,
expected: 4,
actual: 2,
};
let adjusted = err.with_offset(100);
match adjusted {
Error::UnexpectedEndOfInput { offset, .. } => {
assert_eq!(offset, 110);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_error_with_offset_invalid_utf8() {
let err = Error::InvalidUtf8 { offset: 5 };
let adjusted = err.with_offset(100);
match adjusted {
Error::InvalidUtf8 { offset } => {
assert_eq!(offset, 105);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_error_with_offset_invalid_utf16() {
let err = Error::InvalidUtf16 {
offset: 10,
position: 2,
};
let adjusted = err.with_offset(100);
match adjusted {
Error::InvalidUtf16 { offset, position } => {
assert_eq!(offset, 110);
assert_eq!(position, 2);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_error_with_offset_unknown_type() {
let err = Error::UnknownType {
type_byte: 0x42,
offset: 7,
};
let adjusted = err.with_offset(100);
match adjusted {
Error::UnknownType { type_byte, offset } => {
assert_eq!(type_byte, 0x42);
assert_eq!(offset, 107);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_error_with_offset_parse_error() {
let err = Error::ParseError {
input: "test".to_string(),
offset: 3,
context: "context".to_string(),
};
let adjusted = err.with_offset(100);
match adjusted {
Error::ParseError { offset, .. } => {
assert_eq!(offset, 103);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_error_with_offset_no_change_for_non_offset_variants() {
let err = Error::InvalidMagic {
found: 0x12345678,
expected: &[APPINFO_MAGIC_40],
};
let adjusted = err.with_offset(100);
match adjusted {
Error::InvalidMagic { found, .. } => {
assert_eq!(found, 0x12345678);
}
_ => panic!("Unexpected error type"),
}
}
#[test]
fn test_parse_error_truncates_long_input() {
let long_input = "a".repeat(100);
let err = parse_error(&long_input, 0, "test context");
match err {
Error::ParseError { input, .. } => {
assert!(input.len() <= 50, "Input should be truncated to 50 chars");
}
_ => panic!("Expected ParseError variant"),
}
}
#[test]
fn test_with_offset_closure() {
let base_offset = 100;
let f = with_offset(base_offset);
let err = Error::InvalidUtf8 { offset: 5 };
let adjusted = f(err);
match adjusted {
Error::InvalidUtf8 { offset } => {
assert_eq!(offset, 105);
}
_ => panic!("Unexpected error type"),
}
}
}