Skip to main content

asdf_core/
error.rs

1//! Errors, mapped onto the codes the C API reports.
2
3use core::fmt;
4
5/// The error codes `asdf_error_code` reports.
6///
7/// The discriminants are part of the C ABI and must not be reordered.
8#[derive(Clone, Copy, PartialEq, Eq, Debug)]
9#[repr(i32)]
10pub enum ErrorCode {
11    /// No error.
12    None = 0,
13    /// Unknown parser state.
14    UnknownState,
15    /// Stream initialization failed.
16    StreamInitFailed,
17    /// Attempted write to a read-only stream or file.
18    StreamReadOnly,
19    /// Invalid ASDF file header.
20    InvalidAsdfHeader,
21    /// Unexpected end of file.
22    UnexpectedEof,
23    /// Invalid block header.
24    InvalidBlockHeader,
25    /// Block magic bytes did not match.
26    BlockMagicMismatch,
27    /// YAML parser initialization failed.
28    YamlParserInitFailed,
29    /// YAML parsing failed.
30    YamlParseFailed,
31    /// Out of memory.
32    OutOfMemory,
33    /// OS-level error; the original `errno` is reported separately.
34    System,
35    /// Invalid argument.
36    InvalidArgument,
37    /// Unknown compression type.
38    UnknownCompression,
39    /// Compression or decompression error.
40    CompressionFailed,
41    /// No serializer registered for an extension.
42    ExtensionNotFound,
43    /// A system limit has been reached.
44    OverLimit,
45}
46
47/// An error from the ASDF engine.
48#[derive(Debug)]
49pub struct Error {
50    code: ErrorCode,
51    message: String,
52    errno: Option<i32>,
53}
54
55impl Error {
56    /// Build an error with a code and message.
57    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
58        Self { code, message: message.into(), errno: None }
59    }
60
61    /// Build an error carrying an OS `errno`.
62    pub fn system(errno: i32, message: impl Into<String>) -> Self {
63        Self { code: ErrorCode::System, message: message.into(), errno: Some(errno) }
64    }
65
66    /// The code the C API reports for this error.
67    pub fn code(&self) -> ErrorCode {
68        self.code
69    }
70
71    /// The OS `errno`, when [`ErrorCode::System`].
72    pub fn errno(&self) -> Option<i32> {
73        self.errno
74    }
75
76    /// The human-readable message.
77    pub fn message(&self) -> &str {
78        &self.message
79    }
80}
81
82impl fmt::Display for Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.write_str(&self.message)
85    }
86}
87
88impl core::error::Error for Error {}
89
90impl From<std::io::Error> for Error {
91    fn from(e: std::io::Error) -> Self {
92        let errno = e.raw_os_error();
93        match errno {
94            Some(n) => Error::system(n, e.to_string()),
95            None => Error::new(ErrorCode::System, e.to_string()),
96        }
97    }
98}
99
100impl From<asdf_yaml::ParseError> for Error {
101    fn from(e: asdf_yaml::ParseError) -> Self {
102        Error::new(ErrorCode::YamlParseFailed, e.to_string())
103    }
104}
105
106/// The engine's result type.
107pub type Result<T> = core::result::Result<T, Error>;
108
109/// Shorthand for building an [`Error`].
110macro_rules! err {
111    ($code:ident, $($arg:tt)*) => {
112        $crate::error::Error::new($crate::error::ErrorCode::$code, format!($($arg)*))
113    };
114}
115
116pub(crate) use err;
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn discriminants_match_the_c_abi() {
124        // These values are baked into compiled C callers; a reorder is an
125        // ABI break, so pin the ones the header documents explicitly.
126        assert_eq!(ErrorCode::None as i32, 0);
127        assert_eq!(ErrorCode::UnknownState as i32, 1);
128        assert_eq!(ErrorCode::StreamInitFailed as i32, 2);
129        assert_eq!(ErrorCode::StreamReadOnly as i32, 3);
130        assert_eq!(ErrorCode::InvalidAsdfHeader as i32, 4);
131        assert_eq!(ErrorCode::UnexpectedEof as i32, 5);
132        assert_eq!(ErrorCode::InvalidBlockHeader as i32, 6);
133        assert_eq!(ErrorCode::BlockMagicMismatch as i32, 7);
134        assert_eq!(ErrorCode::YamlParserInitFailed as i32, 8);
135        assert_eq!(ErrorCode::YamlParseFailed as i32, 9);
136        assert_eq!(ErrorCode::OutOfMemory as i32, 10);
137        assert_eq!(ErrorCode::System as i32, 11);
138        assert_eq!(ErrorCode::InvalidArgument as i32, 12);
139        assert_eq!(ErrorCode::UnknownCompression as i32, 13);
140        assert_eq!(ErrorCode::CompressionFailed as i32, 14);
141        assert_eq!(ErrorCode::ExtensionNotFound as i32, 15);
142        assert_eq!(ErrorCode::OverLimit as i32, 16);
143    }
144
145    #[test]
146    fn io_errors_carry_errno() {
147        let io = std::io::Error::from_raw_os_error(2);
148        let e = Error::from(io);
149        assert_eq!(e.code(), ErrorCode::System);
150        assert_eq!(e.errno(), Some(2));
151    }
152}