use thiserror::Error;
use crate::ChunkId;
pub type Result<T> = std::result::Result<T, AdtError>;
#[derive(Error, Debug)]
pub enum AdtError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid magic bytes: expected {expected}, found {found} at offset {offset}")]
InvalidMagic {
expected: ChunkId,
found: ChunkId,
offset: u64,
},
#[error("Missing required chunk: {0:?}")]
MissingRequiredChunk(ChunkId),
#[error("Invalid chunk combination: {chunk1:?} and {chunk2:?} cannot coexist")]
InvalidChunkCombination {
chunk1: ChunkId,
chunk2: ChunkId,
},
#[error(
"Offset out of bounds for chunk {chunk:?}: offset {offset} at file position {file_position}"
)]
OffsetOutOfBounds {
chunk: ChunkId,
offset: u32,
file_position: u64,
},
#[error("Invalid chunk size for {chunk:?}: expected {expected}, got {actual}")]
InvalidChunkSize {
chunk: ChunkId,
expected: usize,
actual: usize,
},
#[error(
"Invalid subchunk offset for {parent:?}: offset {offset} exceeds chunk size {chunk_size}"
)]
InvalidSubchunkOffset {
parent: ChunkId,
offset: u32,
chunk_size: u32,
},
#[error("Invalid water structure: {0}")]
InvalidWaterStructure(String),
#[error("Invalid texture reference: index {index} exceeds texture count {count}")]
InvalidTextureReference {
index: u32,
count: u32,
},
#[error("Invalid model reference: index {index} exceeds model count {count}")]
InvalidModelReference {
index: u32,
count: u32,
},
#[error("Invalid MCIN entry at index {index}: references non-existent MCNK")]
InvalidMcinEntry {
index: usize,
},
#[error("Chunk parse error for {chunk:?} at offset {offset}: {details}")]
ChunkParseError {
chunk: ChunkId,
offset: u64,
details: String,
},
#[error("binrw error: {0}")]
BinrwError(String),
#[error("UTF-8 conversion error: {0} (using lossy conversion)")]
Utf8Error(String),
#[error("Unknown chunk encountered: {magic:?} at offset {offset} (skipping)")]
UnknownChunk {
magic: [u8; 4],
offset: u64,
},
#[error("Memory limit exceeded: attempted to allocate {requested} bytes, limit is {limit}")]
MemoryLimitExceeded {
requested: usize,
limit: usize,
},
#[error("Version detection failed: {0}")]
VersionDetectionFailed(String),
}
impl From<binrw::Error> for AdtError {
fn from(err: binrw::Error) -> Self {
AdtError::BinrwError(format!("{err}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_formats_correctly() {
let err = AdtError::InvalidMagic {
expected: ChunkId::MCNK,
found: ChunkId::MCLQ,
offset: 0x1000,
};
let display = format!("{err}");
assert!(display.contains("MCNK"));
assert!(display.contains("MCLQ"));
assert!(display.contains("4096"));
}
#[test]
fn error_context_preserved() {
let err = AdtError::InvalidTextureReference {
index: 15,
count: 10,
};
assert!(matches!(err, AdtError::InvalidTextureReference { .. }));
}
#[test]
fn io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let adt_err: AdtError = io_err.into();
assert!(matches!(adt_err, AdtError::Io(_)));
}
#[test]
fn binrw_error_conversion() {
let binrw_err = binrw::Error::AssertFail {
pos: 0x100,
message: "test assertion failed".into(),
};
let adt_err: AdtError = binrw_err.into();
assert!(matches!(adt_err, AdtError::BinrwError(_)));
}
}