okane_core/report/
error.rs

1//! Defines error in report functions.
2
3use 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::book_keeping::{self, BookKeepError};
13
14/// Error arised in report APIs.
15#[derive(thiserror::Error, Debug)]
16pub enum ReportError {
17    Load(#[from] load::LoadError),
18    BookKeep(book_keeping::BookKeepError, Box<ErrorContext>),
19}
20
21impl Display for ReportError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            ReportError::Load(_) => write!(f, "failed to load the given file"),
25            ReportError::BookKeep(err, ctx) => ctx.print(f, err),
26        }
27    }
28}
29
30/// Context of [ReportError], to carry the failure information.
31#[derive(Debug)]
32pub struct ErrorContext {
33    renderer: annotate_snippets::Renderer,
34    path: PathBuf,
35    line_start: usize,
36    text: String,
37    parsed_span: ParsedSpan,
38}
39
40impl ErrorContext {
41    fn print(&self, f: &mut std::fmt::Formatter<'_>, err: &BookKeepError) -> std::fmt::Result {
42        let message = err.to_string();
43        let path = self.path.to_string_lossy();
44        let annotations: Vec<Annotation> = match err {
45            BookKeepError::UndeduciblePostingAmount(first, second) => {
46                vec![
47                    Level::Warning
48                        .span(self.parsed_span.resolve(first.span()))
49                        .label("first posting that requires deduce"),
50                    Level::Error
51                        .span(self.parsed_span.resolve(second.span()))
52                        .label("either this or previous posting must specify the amount"),
53                ]
54            }
55            _ => {
56                // TODO: Add more detailed error into this.
57                // Also, put these logic into BookKeepError.
58                vec![Level::Error.span(0..self.text.len()).label("error occured")]
59            }
60        };
61        let message = Level::Error.title(&message).snippet(
62            Snippet::source(&self.text)
63                .origin(&path)
64                .line_start(self.line_start)
65                .annotations(annotations),
66        );
67        let rendered = self.renderer.render(message);
68        rendered.fmt(f)
69    }
70
71    pub(super) fn new(
72        renderer: annotate_snippets::Renderer,
73        path: PathBuf,
74        pctx: &parse::ParsedContext,
75    ) -> Box<Self> {
76        Box::new(Self {
77            renderer,
78            path,
79            line_start: pctx.compute_line_start(),
80            text: pctx.as_str().to_owned(),
81            parsed_span: pctx.span(),
82        })
83    }
84}