Skip to main content

apr_format/
error.rs

1//! Error type for the sovereign `apr-format` leaf crate.
2//!
3//! # Wrapper error seam (issue #2231, locked decision 3)
4//!
5//! `apr-format` owns its own `AprFormatError`. It deliberately does **not**
6//! share an error crate with `aprender-core`. Instead, `aprender-core` provides
7//! `impl From<AprFormatError> for AprenderError` so the leaf's errors wrap
8//! transparently into the framework's error type at the crate boundary. This
9//! keeps the leaf sovereign (no dependency back on the framework's error type)
10//! while preserving `?`-ergonomics for core consumers.
11
12/// Errors produced while reading or writing the `.apr` container format.
13///
14/// `#[non_exhaustive]` so new variants can be added without a breaking change.
15#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum AprFormatError {
18    /// CRC32 integrity check failed: the stored trailer does not match the
19    /// recomputed checksum of the file body.
20    #[error("Checksum mismatch: expected 0x{expected:08X}, got 0x{actual:08X}")]
21    ChecksumMismatch {
22        /// Checksum stored in the file trailer.
23        expected: u32,
24        /// Checksum recomputed over the file body.
25        actual: u32,
26    },
27
28    /// Invalid or corrupt container structure (bad magic, truncated header,
29    /// unknown model type, out-of-range sizes, …).
30    #[error("Invalid model format: {message}")]
31    FormatError {
32        /// Human-readable description of the format violation.
33        message: String,
34    },
35
36    /// Serialization / deserialization failure (bincode, msgpack, JSON).
37    #[error("Serialization error: {0}")]
38    Serialization(String),
39
40    /// Poka-yoke / Jidoka validation failure (e.g. refusing to save a model
41    /// whose quality gate scored zero).
42    #[error("Validation failed: {message}")]
43    ValidationError {
44        /// Description of the validation failure.
45        message: String,
46    },
47
48    /// Underlying I/O failure (file open / read / write).
49    #[error("I/O error: {0}")]
50    Io(#[from] std::io::Error),
51
52    /// A computed offset fell outside the file/data boundary.
53    #[error("Invalid offset: out of bounds for the supplied buffer")]
54    InvalidOffset,
55
56    /// The declared header / metadata section exceeds the permitted maximum
57    /// (compression-bomb / overflow protection).
58    #[error("Header or metadata section too large")]
59    HeaderTooLarge,
60
61    /// The on-disk format version is newer than this reader supports.
62    #[error("Unsupported format version: found {}.{}, max supported {}.{}", found.0, found.1, supported.0, supported.1)]
63    UnsupportedVersion {
64        /// Version found in the file header.
65        found: (u8, u8),
66        /// Maximum version this reader supports.
67        supported: (u8, u8),
68    },
69}
70
71/// Convenience alias for results in this crate.
72pub type Result<T> = std::result::Result<T, AprFormatError>;
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_checksum_mismatch_display() {
80        let e = AprFormatError::ChecksumMismatch {
81            expected: 0xDEAD_BEEF,
82            actual: 0xCAFE_BABE,
83        };
84        let s = e.to_string();
85        assert!(s.contains("Checksum mismatch"));
86        assert!(s.contains("DEADBEEF"));
87    }
88
89    #[test]
90    fn test_format_error_display() {
91        let e = AprFormatError::FormatError {
92            message: "corrupt header".to_string(),
93        };
94        assert!(e.to_string().contains("corrupt header"));
95    }
96
97    #[test]
98    fn test_io_from() {
99        let io = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
100        let e: AprFormatError = io.into();
101        assert!(matches!(e, AprFormatError::Io(_)));
102    }
103
104    #[test]
105    fn test_unsupported_version_display() {
106        let e = AprFormatError::UnsupportedVersion {
107            found: (3, 0),
108            supported: (1, 0),
109        };
110        assert!(e.to_string().contains("3.0"));
111    }
112}