Skip to main content

msime_engine/
error.rs

1//! The crate's one error type. Display strings are part of the contract: hosts surface them, and the host tests and goldens compare the exact C++ wording, so constructors take the message from `diagnostics` rather than composing new text.
2
3pub type Result<T, E = EngineError> = std::result::Result<T, E>;
4
5#[derive(Debug, thiserror::Error)]
6pub enum EngineError {
7    /// A caller-supplied value was rejected (the C++ `std::invalid_argument`).
8    #[error("{0}")]
9    InvalidArgument(String),
10    /// An operation could not complete (the C++ `std::runtime_error`).
11    #[error("{0}")]
12    Failed(String),
13    /// 别的进程或本进程的另一处正占着这份数据,操作什么也没做,稍后重试即可。目前只有词库维护:拿不到 `DictionaryAccess` 的独占租约(还有会话或另一次维护开着)。
14    #[error("{0}")]
15    Busy(String),
16    #[error(transparent)]
17    Sqlite(#[from] rusqlite::Error),
18    #[error(transparent)]
19    Io(#[from] std::io::Error),
20}
21
22/// [`EngineError`] 的稳定数字码,供宿主和 C ABI 按数字分支,不必解析说明文字。编号与引擎库 C ABI 草案的返回码共用一个空间:0 是成功,4(不支持)、5(revision 不符)、7(会话已毒化)、8(空指针)留给 C ABI 自己的情形,引擎不产生它们。已发布的编号只追加、不改、不复用。
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24#[repr(u32)]
25pub enum ErrorCode {
26    InvalidArgument = 1,
27    Failed = 2,
28    /// SQLite 或文件系统出错([`EngineError::Sqlite`]、[`EngineError::Io`])。
29    Storage = 3,
30    Busy = 6,
31}
32
33impl ErrorCode {
34    pub const fn value(self) -> u32 {
35        self as u32
36    }
37}
38
39impl EngineError {
40    pub fn invalid(message: impl Into<String>) -> Self {
41        Self::InvalidArgument(message.into())
42    }
43
44    pub fn failed(message: impl Into<String>) -> Self {
45        Self::Failed(message.into())
46    }
47
48    pub fn busy(message: impl Into<String>) -> Self {
49        Self::Busy(message.into())
50    }
51
52    /// 这条错误的稳定数字码;说明文字仍是 `Display`。
53    pub fn code(&self) -> ErrorCode {
54        match self {
55            Self::InvalidArgument(_) => ErrorCode::InvalidArgument,
56            Self::Failed(_) => ErrorCode::Failed,
57            Self::Busy(_) => ErrorCode::Busy,
58            Self::Sqlite(_) | Self::Io(_) => ErrorCode::Storage,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    /// 编号一经发布就冻结:这张表改了,C ABI 的调用方会按错的数字分支。
68    #[test]
69    fn error_codes_are_frozen() {
70        let cases = [
71            (EngineError::invalid("x"), 1),
72            (EngineError::failed("x"), 2),
73            (EngineError::Sqlite(rusqlite::Error::InvalidQuery), 3),
74            (EngineError::Io(std::io::Error::other("x")), 3),
75            (EngineError::busy("x"), 6),
76        ];
77        for (error, code) in cases {
78            assert_eq!(error.code().value(), code, "{error:?}");
79        }
80    }
81
82    #[test]
83    fn codes_keep_the_message() {
84        let error = EngineError::busy(crate::diagnostics::DICTIONARY_MAINTENANCE_BUSY);
85        assert_eq!(error.to_string(), "Dictionary maintenance busy");
86        assert_eq!(error.code(), ErrorCode::Busy);
87    }
88}