Anki/rslib/src/backend/card.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

125 lines
4 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use super::Backend;
pub(super) use crate::pb::cards_service::Service as CardsService;
use crate::{
card::{CardQueue, CardType},
pb,
prelude::*,
};
impl CardsService for Backend {
fn get_card(&self, input: pb::CardId) -> Result<pb::Card> {
let cid = input.into();
self.with_col(|col| {
col.storage
.get_card(cid)
.and_then(|opt| opt.or_not_found(cid))
.map(Into::into)
})
}
fn update_cards(&self, input: pb::UpdateCardsRequest) -> Result<pb::OpChanges> {
self.with_col(|col| {
let cards = input
.cards
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<Card>, AnkiError>>()?;
for card in &cards {
card.validate_custom_data()?;
}
col.update_cards_maybe_undoable(cards, !input.skip_undo_entry)
})
.map(Into::into)
}
fn remove_cards(&self, input: pb::RemoveCardsRequest) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact_no_undo(|col| {
col.remove_cards_and_orphaned_notes(
&input
.card_ids
.into_iter()
.map(Into::into)
.collect::<Vec<_>>(),
)?;
Ok(().into())
})
})
}
fn set_deck(&self, input: pb::SetDeckRequest) -> Result<pb::OpChangesWithCount> {
let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect();
let deck_id = input.deck_id.into();
self.with_col(|col| col.set_deck(&cids, deck_id).map(Into::into))
}
fn set_flag(&self, input: pb::SetFlagRequest) -> Result<pb::OpChangesWithCount> {
self.with_col(|col| {
col.set_card_flag(&to_card_ids(input.card_ids), input.flag)
.map(Into::into)
})
}
}
impl TryFrom<pb::Card> for Card {
type Error = AnkiError;
fn try_from(c: pb::Card) -> Result<Self, Self::Error> {
let ctype = CardType::try_from(c.ctype as u8).or_invalid("invalid card type")?;
let queue = CardQueue::try_from(c.queue as i8).or_invalid("invalid card queue")?;
Ok(Card {
id: CardId(c.id),
note_id: NoteId(c.note_id),
deck_id: DeckId(c.deck_id),
template_idx: c.template_idx as u16,
mtime: TimestampSecs(c.mtime_secs),
usn: Usn(c.usn),
ctype,
queue,
due: c.due,
interval: c.interval,
ease_factor: c.ease_factor as u16,
reps: c.reps,
lapses: c.lapses,
remaining_steps: c.remaining_steps,
original_due: c.original_due,
original_deck_id: DeckId(c.original_deck_id),
flags: c.flags as u8,
original_position: c.original_position,
custom_data: c.custom_data,
})
}
}
impl From<Card> for pb::Card {
fn from(c: Card) -> Self {
pb::Card {
id: c.id.0,
note_id: c.note_id.0,
deck_id: c.deck_id.0,
template_idx: c.template_idx as u32,
mtime_secs: c.mtime.0,
usn: c.usn.0,
ctype: c.ctype as u32,
queue: c.queue as i32,
due: c.due,
interval: c.interval,
ease_factor: c.ease_factor as u32,
reps: c.reps,
lapses: c.lapses,
remaining_steps: c.remaining_steps,
original_due: c.original_due,
original_deck_id: c.original_deck_id.0,
flags: c.flags as u32,
original_position: c.original_position.map(Into::into),
custom_data: c.custom_data,
}
}
}
fn to_card_ids(v: Vec<i64>) -> Vec<CardId> {
v.into_iter().map(CardId).collect()
}