okane_core/report/
error.rs1use std::{fmt::Display, path::PathBuf};
4
5use annotate_snippets::{Annotation, Level, Snippet};
6
7use crate::{
8 load,
9 parse::{self, ParsedSpan},
10};
11
12use super::{
13 book_keeping::{self, BookKeepError},
14 price_db,
15};
16
17#[derive(thiserror::Error, Debug)]
19pub enum ReportError {
20 Load(#[from] load::LoadError),
21 PriceDB(#[from] price_db::LoadError),
22 BookKeep(book_keeping::BookKeepError, Box<ErrorContext>),
23}
24
25impl Display for ReportError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 ReportError::Load(_) => write!(f, "failed to load the given file"),
29 ReportError::PriceDB(_) => write!(f, "failed to load the Price DB"),
30 ReportError::BookKeep(err, ctx) => ctx.print(f, err),
31 }
32 }
33}
34
35#[derive(Debug)]
37pub struct ErrorContext {
38 renderer: annotate_snippets::Renderer,
39 path: PathBuf,
40 line_start: usize,
41 text: String,
42 parsed_span: ParsedSpan,
43}
44
45impl ErrorContext {
46 fn print(&self, f: &mut std::fmt::Formatter<'_>, err: &BookKeepError) -> std::fmt::Result {
47 let message = err.to_string();
48 let path = self.path.to_string_lossy();
49 let annotations: Vec<Annotation> = match err {
50 BookKeepError::UndeduciblePostingAmount(first, second) => vec![
51 Level::Warning
52 .span(self.parsed_span.resolve(&first.span()))
53 .label("first posting without constraints"),
54 Level::Error
55 .span(self.parsed_span.resolve(&second.span()))
56 .label("cannot deduce this posting"),
57 ],
58 BookKeepError::ZeroAmountWithExchange(exchange) => vec![Level::Error
59 .span(self.parsed_span.resolve(exchange))
60 .label("absolute zero posting should not have exchange")],
61 BookKeepError::ZeroExchangeRate(exchange) => vec![Level::Error
62 .span(self.parsed_span.resolve(exchange))
63 .label("exchange with zero amount")],
64 BookKeepError::ExchangeWithAmountCommodity {
65 posting_amount,
66 exchange,
67 } => vec![
68 Level::Info
69 .span(self.parsed_span.resolve(posting_amount))
70 .label("posting amount"),
71 Level::Error
72 .span(self.parsed_span.resolve(exchange))
73 .label("exchange cannot have the same commodity with posting"),
74 ],
75 _ => {
76 vec![Level::Error.span(0..self.text.len()).label("error occured")]
79 }
80 };
81 let message = Level::Error.title(&message).snippet(
82 Snippet::source(&self.text)
83 .origin(&path)
84 .line_start(self.line_start)
85 .annotations(annotations),
86 );
87 let rendered = self.renderer.render(message);
88 rendered.fmt(f)
89 }
90
91 pub(super) fn new(
92 renderer: annotate_snippets::Renderer,
93 path: PathBuf,
94 pctx: &parse::ParsedContext,
95 ) -> Box<Self> {
96 Box::new(Self {
97 renderer,
98 path,
99 line_start: pctx.compute_line_start(),
100 text: pctx.as_str().to_owned(),
101 parsed_span: pctx.span(),
102 })
103 }
104}