Anki/rslib/src/error/db.rs
RumovZ c521753057
Refactor error handling (#2136)
* Add crate snafu

* Replace all inline structs in AnkiError

* Derive Snafu on AnkiError

* Use snafu for card type errors

* Use snafu whatever error for InvalidInput

* Use snafu for NotFoundError and improve message

* Use snafu for FileIoError to attach context

Remove IoError.
Add some context-attaching helpers to replace code returning bare
io::Errors.

* Add more context-attaching io helpers

* Add message, context and backtrace to new snafus

* Utilize error context and backtrace on frontend

* Rename LocalizedError -> BackendError.
* Remove DocumentedError.
* Have all backend exceptions inherit BackendError.

* Rename localized(_description) -> message

* Remove accidentally committed experimental trait

* invalid_input_context -> ok_or_invalid

* ensure_valid_input! -> require!

* Always return `Err` from `invalid_input!`

Instead of a Result to unwrap, the macro accepts a source error now.

* new_tempfile_in_parent -> new_tempfile_in_parent_of

* ok_or_not_found -> or_not_found

* ok_or_invalid -> or_invalid

* Add crate convert_case

* Use unqualified lowercase type name

* Remove uses of snafu::ensure

* Allow public construction of InvalidInputErrors (dae)

Needed to port the AnkiDroid changes.

* Make into_protobuf() public (dae)

Also required for AnkiDroid. Not sure why it worked previously - possible
bug in older Rust version?
2022-10-21 18:02:12 +10:00

96 lines
2.5 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::str::Utf8Error;
use anki_i18n::I18n;
use rusqlite::{types::FromSqlError, Error};
use snafu::Snafu;
use super::AnkiError;
#[derive(Debug, PartialEq, Eq, Snafu)]
pub struct DbError {
pub info: String,
pub kind: DbErrorKind,
}
#[derive(Debug, PartialEq, Eq)]
pub enum DbErrorKind {
FileTooNew,
FileTooOld,
MissingEntity,
Corrupt,
Locked,
Utf8,
Other,
}
impl AnkiError {
pub(crate) fn db_error(info: impl Into<String>, kind: DbErrorKind) -> Self {
AnkiError::DbError {
source: DbError {
info: info.into(),
kind,
},
}
}
}
impl From<Error> for AnkiError {
fn from(err: Error) -> Self {
if let Error::SqliteFailure(error, Some(reason)) = &err {
if error.code == rusqlite::ErrorCode::DatabaseBusy {
return AnkiError::DbError {
source: DbError {
info: "".to_string(),
kind: DbErrorKind::Locked,
},
};
}
if reason.contains("regex parse error") {
return AnkiError::InvalidRegex {
info: reason.to_owned(),
};
}
}
AnkiError::DbError {
source: DbError {
info: format!("{:?}", err),
kind: DbErrorKind::Other,
},
}
}
}
impl From<FromSqlError> for AnkiError {
fn from(err: FromSqlError) -> Self {
if let FromSqlError::Other(ref err) = err {
if let Some(_err) = err.downcast_ref::<Utf8Error>() {
return AnkiError::DbError {
source: DbError {
info: "".to_string(),
kind: DbErrorKind::Utf8,
},
};
}
}
AnkiError::DbError {
source: DbError {
info: format!("{:?}", err),
kind: DbErrorKind::Other,
},
}
}
}
impl DbError {
pub fn message(&self, _tr: &I18n) -> String {
match self.kind {
DbErrorKind::Corrupt => self.info.clone(),
// fixme: i18n
DbErrorKind::Locked => "Anki already open, or media currently syncing.".into(),
_ => format!("{:?}", self),
}
}
}