Anki/rslib/src/error/search.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

134 lines
4.8 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::num::ParseIntError;
use anki_i18n::I18n;
use nom::error::{ErrorKind as NomErrorKind, ParseError as NomParseError};
use snafu::Snafu;
use super::AnkiError;
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
Anki(&'a str, SearchErrorKind),
Nom(&'a str, NomErrorKind),
}
#[derive(Debug, PartialEq, Eq, Snafu)]
pub enum SearchErrorKind {
MisplacedAnd,
MisplacedOr,
EmptyGroup,
UnopenedGroup,
UnclosedGroup,
EmptyQuote,
UnclosedQuote,
MissingKey,
UnknownEscape { provided: String },
InvalidState { provided: String },
InvalidFlag,
InvalidPropProperty { provided: String },
InvalidPropOperator { provided: String },
InvalidNumber { provided: String, context: String },
InvalidWholeNumber { provided: String, context: String },
InvalidPositiveWholeNumber { provided: String, context: String },
InvalidNegativeWholeNumber { provided: String, context: String },
InvalidAnswerButton { provided: String, context: String },
Other { info: Option<String> },
}
impl From<ParseError<'_>> for AnkiError {
fn from(err: ParseError) -> Self {
match err {
ParseError::Anki(_, kind) => AnkiError::SearchError { source: kind },
ParseError::Nom(_, _) => AnkiError::SearchError {
source: SearchErrorKind::Other { info: None },
},
}
}
}
impl From<nom::Err<ParseError<'_>>> for AnkiError {
fn from(err: nom::Err<ParseError<'_>>) -> Self {
match err {
nom::Err::Error(e) => e.into(),
nom::Err::Failure(e) => e.into(),
nom::Err::Incomplete(_) => AnkiError::SearchError {
source: SearchErrorKind::Other { info: None },
},
}
}
}
impl<'a> NomParseError<&'a str> for ParseError<'a> {
fn from_error_kind(input: &'a str, kind: NomErrorKind) -> Self {
ParseError::Nom(input, kind)
}
fn append(_: &str, _: NomErrorKind, other: Self) -> Self {
other
}
}
impl From<ParseIntError> for AnkiError {
fn from(_err: ParseIntError) -> Self {
AnkiError::ParseNumError
}
}
impl SearchErrorKind {
pub fn message(&self, tr: &I18n) -> String {
let reason = match self {
SearchErrorKind::MisplacedAnd => tr.search_misplaced_and(),
SearchErrorKind::MisplacedOr => tr.search_misplaced_or(),
SearchErrorKind::EmptyGroup => tr.search_empty_group(),
SearchErrorKind::UnopenedGroup => tr.search_unopened_group(),
SearchErrorKind::UnclosedGroup => tr.search_unclosed_group(),
SearchErrorKind::EmptyQuote => tr.search_empty_quote(),
SearchErrorKind::UnclosedQuote => tr.search_unclosed_quote(),
SearchErrorKind::MissingKey => tr.search_missing_key(),
SearchErrorKind::UnknownEscape { provided } => {
tr.search_unknown_escape(provided.replace('`', "'"))
}
SearchErrorKind::InvalidState { provided } => {
tr.search_invalid_argument("is:", provided.replace('`', "'"))
}
SearchErrorKind::InvalidFlag => tr.search_invalid_flag_2(),
SearchErrorKind::InvalidPropProperty { provided } => {
tr.search_invalid_argument("prop:", provided.replace('`', "'"))
}
SearchErrorKind::InvalidPropOperator { provided } => {
tr.search_invalid_prop_operator(provided.as_str())
}
SearchErrorKind::Other { info: Some(info) } => info.into(),
SearchErrorKind::Other { info: None } => tr.search_invalid_other(),
SearchErrorKind::InvalidNumber { provided, context } => {
tr.search_invalid_number(context.replace('`', "'"), provided.replace('`', "'"))
}
SearchErrorKind::InvalidWholeNumber { provided, context } => tr
.search_invalid_whole_number(context.replace('`', "'"), provided.replace('`', "'")),
SearchErrorKind::InvalidPositiveWholeNumber { provided, context } => tr
.search_invalid_positive_whole_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidNegativeWholeNumber { provided, context } => tr
.search_invalid_negative_whole_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidAnswerButton { provided, context } => tr
.search_invalid_answer_button(
context.replace('`', "'"),
provided.replace('`', "'"),
),
};
tr.search_invalid_search(reason).into()
}
}