pub type Result<T, E = EngineError> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum EngineError {
#[error("{0}")]
InvalidArgument(String),
#[error("{0}")]
Failed(String),
#[error("{0}")]
Busy(String),
#[error(transparent)]
Sqlite(#[from] rusqlite::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ErrorCode {
InvalidArgument = 1,
Failed = 2,
Storage = 3,
Busy = 6,
}
impl ErrorCode {
pub const fn value(self) -> u32 {
self as u32
}
}
impl EngineError {
pub fn invalid(message: impl Into<String>) -> Self {
Self::InvalidArgument(message.into())
}
pub fn failed(message: impl Into<String>) -> Self {
Self::Failed(message.into())
}
pub fn busy(message: impl Into<String>) -> Self {
Self::Busy(message.into())
}
pub fn code(&self) -> ErrorCode {
match self {
Self::InvalidArgument(_) => ErrorCode::InvalidArgument,
Self::Failed(_) => ErrorCode::Failed,
Self::Busy(_) => ErrorCode::Busy,
Self::Sqlite(_) | Self::Io(_) => ErrorCode::Storage,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_codes_are_frozen() {
let cases = [
(EngineError::invalid("x"), 1),
(EngineError::failed("x"), 2),
(EngineError::Sqlite(rusqlite::Error::InvalidQuery), 3),
(EngineError::Io(std::io::Error::other("x")), 3),
(EngineError::busy("x"), 6),
];
for (error, code) in cases {
assert_eq!(error.code().value(), code, "{error:?}");
}
}
#[test]
fn codes_keep_the_message() {
let error = EngineError::busy(crate::diagnostics::DICTIONARY_MAINTENANCE_BUSY);
assert_eq!(error.to_string(), "Dictionary maintenance busy");
assert_eq!(error.code(), ErrorCode::Busy);
}
}