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

184 lines
6.1 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::cardrendering_service::Service as CardRenderingService;
use crate::{
card_rendering::{extract_av_tags, strip_av_tags},
latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex},
markdown::render_markdown,
notetype::{CardTemplateSchema11, RenderCardOutput},
pb,
prelude::*,
template::RenderedNode,
text::{
decode_iri_paths, encode_iri_paths, sanitize_html_no_images, strip_html,
strip_html_preserving_media_filenames,
},
typeanswer::compare_answer,
};
impl CardRenderingService for Backend {
fn extract_av_tags(
&self,
input: pb::ExtractAvTagsRequest,
) -> Result<pb::ExtractAvTagsResponse> {
let out = extract_av_tags(input.text, input.question_side, self.i18n());
Ok(pb::ExtractAvTagsResponse {
text: out.0,
av_tags: out.1,
})
}
fn extract_latex(&self, input: pb::ExtractLatexRequest) -> Result<pb::ExtractLatexResponse> {
let func = if input.expand_clozes {
extract_latex_expanding_clozes
} else {
extract_latex
};
let (text, extracted) = func(&input.text, input.svg);
Ok(pb::ExtractLatexResponse {
text,
latex: extracted
.into_iter()
.map(|e: ExtractedLatex| pb::ExtractedLatex {
filename: e.fname,
latex_body: e.latex,
})
.collect(),
})
}
fn get_empty_cards(&self, _input: pb::Empty) -> Result<pb::EmptyCardsReport> {
self.with_col(|col| {
let mut empty = col.empty_cards()?;
let report = col.empty_cards_report(&mut empty)?;
let mut outnotes = vec![];
for (_ntid, notes) in empty {
outnotes.extend(notes.into_iter().map(|e| {
pb::empty_cards_report::NoteWithEmptyCards {
note_id: e.nid.0,
will_delete_note: e.empty.len() == e.current_count,
card_ids: e.empty.into_iter().map(|(_ord, id)| id.0).collect(),
}
}))
}
Ok(pb::EmptyCardsReport {
report,
notes: outnotes,
})
})
}
fn render_existing_card(
&self,
input: pb::RenderExistingCardRequest,
) -> Result<pb::RenderCardResponse> {
self.with_col(|col| {
col.render_existing_card(CardId(input.card_id), input.browser)
.map(Into::into)
})
}
fn render_uncommitted_card(
&self,
input: pb::RenderUncommittedCardRequest,
) -> Result<pb::RenderCardResponse> {
let template = input.template.or_invalid("missing template")?.into();
let mut note = input.note.or_invalid("missing note")?.into();
let ord = input.card_ord as u16;
let fill_empty = input.fill_empty;
self.with_col(|col| {
col.render_uncommitted_card(&mut note, &template, ord, fill_empty)
.map(Into::into)
})
}
fn render_uncommitted_card_legacy(
&self,
input: pb::RenderUncommittedCardLegacyRequest,
) -> Result<pb::RenderCardResponse> {
let schema11: CardTemplateSchema11 = serde_json::from_slice(&input.template)?;
let template = schema11.into();
let mut note = input.note.or_invalid("missing note")?.into();
let ord = input.card_ord as u16;
let fill_empty = input.fill_empty;
self.with_col(|col| {
col.render_uncommitted_card(&mut note, &template, ord, fill_empty)
.map(Into::into)
})
}
fn strip_av_tags(&self, input: pb::String) -> Result<pb::String> {
Ok(strip_av_tags(input.val).into())
}
fn render_markdown(&self, input: pb::RenderMarkdownRequest) -> Result<pb::String> {
let mut text = render_markdown(&input.markdown);
if input.sanitize {
// currently no images
text = sanitize_html_no_images(&text);
}
Ok(text.into())
}
fn encode_iri_paths(&self, input: pb::String) -> Result<pb::String> {
Ok(encode_iri_paths(&input.val).to_string().into())
}
fn decode_iri_paths(&self, input: pb::String) -> Result<pb::String> {
Ok(decode_iri_paths(&input.val).to_string().into())
}
fn strip_html(&self, input: pb::StripHtmlRequest) -> Result<pb::String> {
Ok(match input.mode() {
pb::strip_html_request::Mode::Normal => strip_html(&input.text),
pb::strip_html_request::Mode::PreserveMediaFilenames => {
strip_html_preserving_media_filenames(&input.text)
}
}
.to_string()
.into())
}
fn compare_answer(&self, input: pb::CompareAnswerRequest) -> Result<pb::String> {
Ok(compare_answer(&input.expected, &input.provided).into())
}
}
fn rendered_nodes_to_proto(nodes: Vec<RenderedNode>) -> Vec<pb::RenderedTemplateNode> {
nodes
.into_iter()
.map(|n| pb::RenderedTemplateNode {
value: Some(rendered_node_to_proto(n)),
})
.collect()
}
fn rendered_node_to_proto(node: RenderedNode) -> pb::rendered_template_node::Value {
match node {
RenderedNode::Text { text } => pb::rendered_template_node::Value::Text(text),
RenderedNode::Replacement {
field_name,
current_text,
filters,
} => pb::rendered_template_node::Value::Replacement(pb::RenderedTemplateReplacement {
field_name,
current_text,
filters,
}),
}
}
impl From<RenderCardOutput> for pb::RenderCardResponse {
fn from(o: RenderCardOutput) -> Self {
pb::RenderCardResponse {
question_nodes: rendered_nodes_to_proto(o.qnodes),
answer_nodes: rendered_nodes_to_proto(o.anodes),
css: o.css,
latex_svg: o.latex_svg,
}
}
}