Anki/rslib/src/error/mod.rs
RumovZ f1488b5983
Card type error (#1749)
* TemplateSaveError -> CardTypeError

* Don't show success tooltip if export fails

* Attach help page to error

Show help link if export fails due to card type error.

* Add type (dae)

* Add shared show_exception() (dae)

- Use a shared routine for printing standard backend errors, so that
we can take advantage of the help links in eg. the card layout screen
as well.
- The truthiness check on help in showInfo() would have ignored the
enum 0 value.
- Close the exporting dialog on a documented failure as well

* Fix local variable help_page
2022-03-28 22:17:50 +10:00

243 lines
7.7 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod db;
mod filtered;
mod network;
mod search;
use std::{fmt::Display, io, path::Path};
pub use db::{DbError, DbErrorKind};
pub use filtered::{CustomStudyError, FilteredDeckError};
pub use network::{NetworkError, NetworkErrorKind, SyncError, SyncErrorKind};
pub use search::{ParseError, SearchErrorKind};
use tempfile::PathPersistError;
use crate::{i18n::I18n, links::HelpPage};
pub type Result<T, E = AnkiError> = std::result::Result<T, E>;
#[derive(Debug, PartialEq)]
pub enum AnkiError {
InvalidInput(String),
TemplateError(String),
CardTypeError(CardTypeError),
IoError(String),
FileIoError(FileIoError),
DbError(DbError),
NetworkError(NetworkError),
SyncError(SyncError),
JsonError(String),
ProtoError(String),
ParseNumError,
Interrupted,
CollectionNotOpen,
CollectionAlreadyOpen,
NotFound,
/// Indicates an absent card or note, but (unlike [AnkiError::NotFound]) in
/// a non-critical context like the browser table, where deleted ids are
/// deliberately not removed.
Deleted,
Existing,
FilteredDeckError(FilteredDeckError),
SearchError(SearchErrorKind),
InvalidRegex(String),
UndoEmpty,
MultipleNotetypesSelected,
DatabaseCheckRequired,
MediaCheckRequired,
CustomStudyError(CustomStudyError),
ImportError(ImportError),
}
impl Display for AnkiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
// error helpers
impl AnkiError {
pub(crate) fn invalid_input<S: Into<String>>(s: S) -> AnkiError {
AnkiError::InvalidInput(s.into())
}
pub fn localized_description(&self, tr: &I18n) -> String {
match self {
AnkiError::SyncError(err) => err.localized_description(tr),
AnkiError::NetworkError(err) => err.localized_description(tr),
AnkiError::TemplateError(info) => {
// already localized
info.into()
}
AnkiError::CardTypeError(err) => {
let header =
tr.card_templates_invalid_template_number(err.ordinal + 1, &err.notetype);
let details = match err.details {
CardTypeErrorDetails::TemplateError | CardTypeErrorDetails::NoSuchField => {
tr.card_templates_see_preview()
}
CardTypeErrorDetails::NoFrontField => tr.card_templates_no_front_field(),
CardTypeErrorDetails::Duplicate(i) => tr.card_templates_identical_front(i + 1),
CardTypeErrorDetails::MissingCloze => tr.card_templates_missing_cloze(),
CardTypeErrorDetails::ExtraneousCloze => tr.card_templates_extraneous_cloze(),
};
format!("{}<br>{}", header, details)
}
AnkiError::DbError(err) => err.localized_description(tr),
AnkiError::SearchError(kind) => kind.localized_description(tr),
AnkiError::InvalidInput(info) => {
if info.is_empty() {
tr.errors_invalid_input_empty().into()
} else {
tr.errors_invalid_input_details(info.as_str()).into()
}
}
AnkiError::ParseNumError => tr.errors_parse_number_fail().into(),
AnkiError::FilteredDeckError(err) => err.localized_description(tr),
AnkiError::InvalidRegex(err) => format!("<pre>{}</pre>", err),
AnkiError::MultipleNotetypesSelected => tr.errors_multiple_notetypes_selected().into(),
AnkiError::DatabaseCheckRequired => tr.errors_please_check_database().into(),
AnkiError::MediaCheckRequired => tr.errors_please_check_media().into(),
AnkiError::CustomStudyError(err) => err.localized_description(tr),
AnkiError::ImportError(err) => err.localized_description(tr),
AnkiError::Deleted => tr.browsing_row_deleted().into(),
AnkiError::IoError(_)
| AnkiError::JsonError(_)
| AnkiError::ProtoError(_)
| AnkiError::Interrupted
| AnkiError::CollectionNotOpen
| AnkiError::CollectionAlreadyOpen
| AnkiError::NotFound
| AnkiError::Existing
| AnkiError::UndoEmpty => format!("{:?}", self),
AnkiError::FileIoError(err) => {
format!("{}: {}", err.path, err.error)
}
}
}
pub fn help_page(&self) -> Option<HelpPage> {
match self {
Self::CardTypeError(CardTypeError { details, .. }) => Some(match details {
CardTypeErrorDetails::TemplateError | CardTypeErrorDetails::NoSuchField => {
HelpPage::CardTypeTemplateError
}
CardTypeErrorDetails::Duplicate(_) => HelpPage::CardTypeDuplicate,
CardTypeErrorDetails::NoFrontField => HelpPage::CardTypeNoFrontField,
CardTypeErrorDetails::MissingCloze => HelpPage::CardTypeMissingCloze,
CardTypeErrorDetails::ExtraneousCloze => HelpPage::CardTypeExtraneousCloze,
}),
_ => None,
}
}
}
#[derive(Debug, PartialEq)]
pub enum TemplateError {
NoClosingBrackets(String),
ConditionalNotClosed(String),
ConditionalNotOpen {
closed: String,
currently_open: Option<String>,
},
FieldNotFound {
filters: String,
field: String,
},
NoSuchConditional(String),
}
impl From<io::Error> for AnkiError {
fn from(err: io::Error) -> Self {
AnkiError::IoError(format!("{:?}", err))
}
}
impl From<serde_json::Error> for AnkiError {
fn from(err: serde_json::Error) -> Self {
AnkiError::JsonError(err.to_string())
}
}
impl From<prost::EncodeError> for AnkiError {
fn from(err: prost::EncodeError) -> Self {
AnkiError::ProtoError(err.to_string())
}
}
impl From<prost::DecodeError> for AnkiError {
fn from(err: prost::DecodeError) -> Self {
AnkiError::ProtoError(err.to_string())
}
}
impl From<PathPersistError> for AnkiError {
fn from(e: PathPersistError) -> Self {
AnkiError::IoError(e.to_string())
}
}
impl From<regex::Error> for AnkiError {
fn from(err: regex::Error) -> Self {
AnkiError::InvalidRegex(err.to_string())
}
}
#[derive(Debug, PartialEq)]
pub struct CardTypeError {
pub notetype: String,
pub ordinal: usize,
pub details: CardTypeErrorDetails,
}
#[derive(Debug, PartialEq)]
pub enum CardTypeErrorDetails {
TemplateError,
Duplicate(usize),
NoFrontField,
NoSuchField,
MissingCloze,
ExtraneousCloze,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ImportError {
Corrupt,
TooNew,
MediaImportFailed(String),
}
impl ImportError {
fn localized_description(&self, tr: &I18n) -> String {
match self {
ImportError::Corrupt => tr.importing_the_provided_file_is_not_a(),
ImportError::TooNew => tr.errors_collection_too_new(),
ImportError::MediaImportFailed(err) => tr.importing_failed_to_import_media_file(err),
}
.into()
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FileIoError {
pub path: String,
pub error: String,
}
impl AnkiError {
pub(crate) fn file_io_error<P: AsRef<Path>>(err: std::io::Error, path: P) -> Self {
AnkiError::FileIoError(FileIoError::new(err, path.as_ref()))
}
}
impl FileIoError {
pub fn new(err: std::io::Error, path: &Path) -> FileIoError {
FileIoError {
path: path.to_string_lossy().to_string(),
error: err.to_string(),
}
}
}