use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct Error {
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
pub message: String,
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn new(code: &str, reason: Option<&str>, message: impl Into<String>) -> Self {
Self { code: code.into(), reason: reason.map(Into::into), message: message.into() }
}
pub fn permission_denied(reason: &str) -> Self {
Self::new("permission_denied", Some(reason), reason)
}
pub fn validation(reason: &str, message: impl Into<String>) -> Self {
Self::new("local_validation", Some(reason), message)
}
pub fn invalid_state(reason: &str) -> Self {
Self::new("invalid_state", Some(reason), reason)
}
pub fn unsupported(reason: &str) -> Self {
Self::new("unsupported", Some(reason), reason)
}
pub fn storage(reason: &str, message: impl Into<String>) -> Self {
Self::new("storage_error", Some(reason), message)
}
pub fn aborted() -> Self {
Self::new("aborted", None, "aborted")
}
pub fn network(message: impl Into<String>) -> Self {
Self::new("network_error", None, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new("internal", None, message)
}
pub fn not_engine() -> Self {
Self::permission_denied("not_engine_window")
}
pub fn from_io_read(err: &std::io::Error) -> Self {
match err.kind() {
std::io::ErrorKind::NotFound => Self::validation("file_not_found", err.to_string()),
std::io::ErrorKind::PermissionDenied => Self::validation("file_unreadable", err.to_string()),
_ => Self::from_io(err),
}
}
pub fn from_io(err: &std::io::Error) -> Self {
if is_disk_full(err) {
return Self::storage("disk_full", err.to_string());
}
match err.kind() {
std::io::ErrorKind::NotFound => Self::validation("file_not_found", err.to_string()),
_ => Self::storage("io", err.to_string()),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.reason {
Some(reason) => write!(f, "{}({}):{}", self.code, reason, self.message),
None => write!(f, "{}:{}", self.code, self.message),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::from_io(&err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::new("local_validation", Some("invalid_json"), err.to_string())
}
}
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Self {
if let rusqlite::Error::SqliteFailure(e, _) = &err {
if e.code == rusqlite::ErrorCode::DiskFull {
return Self::storage("quota_exceeded", err.to_string());
}
}
Self::storage("sqlite", err.to_string())
}
}
impl From<tauri::Error> for Error {
fn from(err: tauri::Error) -> Self {
Self::internal(err.to_string())
}
}
pub fn is_disk_full(err: &std::io::Error) -> bool {
if err.kind() == std::io::ErrorKind::StorageFull {
return true;
}
match err.raw_os_error() {
#[cfg(unix)]
Some(28) => true,
#[cfg(windows)]
Some(39) | Some(112) => true,
_ => false,
}
}