1use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum AprError {
10 #[error("Invalid APR file: expected magic 'APNR', got {found:?}")]
12 InvalidMagic {
13 found: [u8; 4],
15 },
16
17 #[error("Unsupported APR version: {version} (supported: {min_supported}+)")]
19 UnsupportedVersion {
20 version: u16,
22 min_supported: u16,
24 },
25
26 #[error("APR file corrupted: checksum mismatch (expected {expected:08x}, got {computed:08x})")]
28 ChecksumMismatch {
29 expected: u32,
31 computed: u32,
33 },
34
35 #[error("Model too large: {size} bytes (max: {max} bytes)")]
37 ModelTooLarge {
38 size: usize,
40 max: usize,
42 },
43
44 #[error("APR file too small: {size} bytes (minimum header: 10 bytes)")]
46 FileTooSmall {
47 size: usize,
49 },
50
51 #[error("Invalid model name: '{name}' (must be 3-50 alphanumeric/hyphen chars)")]
53 InvalidName {
54 name: String,
56 },
57
58 #[error("Invalid version string: {version}")]
60 InvalidVersion {
61 version: String,
63 },
64
65 #[error("CBOR encoding error: {0}")]
67 CborEncode(String),
68
69 #[error("CBOR decoding error: {0}")]
71 CborDecode(String),
72
73 #[error("Compression error: {0}")]
75 Compression(String),
76
77 #[error("Decompression error: {0}")]
79 Decompression(String),
80
81 #[error("Unknown builtin model: '{name}' (available: chase, patrol, wander)")]
83 UnknownBuiltin {
84 name: String,
86 },
87
88 #[error("Missing required field: {field}")]
90 MissingField {
91 field: &'static str,
93 },
94
95 #[error("I/O error: {0}")]
97 Io(#[from] std::io::Error),
98}
99
100impl AprError {
101 #[must_use]
103 pub fn invalid_magic(bytes: &[u8]) -> Self {
104 let mut found = [0u8; 4];
105 let len = bytes.len().min(4);
106 found[..len].copy_from_slice(&bytes[..len]);
107 Self::InvalidMagic { found }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_error_display() {
117 let err = AprError::InvalidMagic { found: *b"BAAD" };
118 assert!(err.to_string().contains("APNR"));
119 }
120
121 #[test]
122 fn test_checksum_error_hex_format() {
123 let err = AprError::ChecksumMismatch {
124 expected: 0xDEAD_BEEF,
125 computed: 0xCAFE_BABE,
126 };
127 let msg = err.to_string();
128 assert!(msg.contains("deadbeef"));
129 assert!(msg.contains("cafebabe"));
130 }
131
132 #[test]
133 fn test_model_too_large_error() {
134 let err = AprError::ModelTooLarge {
135 size: 2_000_000,
136 max: 1_048_576,
137 };
138 assert!(err.to_string().contains("2000000"));
139 assert!(err.to_string().contains("1048576"));
140 }
141}