serde_hkx/errors/
readable.rs

1//! HexDump Display(For binary)/XML human-readable error message
2use crate::lib::*;
3use winnow::error::{ContextError, ErrMode, ParseError, StrContext};
4
5/// Error struct to represent parsing errors in a more user-friendly way.
6#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
7pub struct ReadableError {
8    title: String,
9    message: String,
10    span: Range<usize>,
11    input: String,
12}
13
14impl ReadableError {
15    /// Constructs [`Self`] from parse error & input.
16    pub fn from_parse(error: ParseError<&str, ContextError>, input: &str) -> Self {
17        let message = error.inner().to_string();
18        let input = input.to_string();
19        let start = error.offset();
20        let end = (start + 1..)
21            .find(|e| input.is_char_boundary(*e))
22            .unwrap_or(start);
23        Self {
24            title: "Parse error".to_string(),
25            message,
26            span: start..end,
27            input,
28        }
29    }
30
31    /// Constructs [`Self`] from parse error & input.
32    pub fn from_context<T>(error: ErrMode<ContextError>, input: T, err_pos: usize) -> Self
33    where
34        T: core::fmt::Display,
35    {
36        let (labels, message) = error
37            .map(|ctx_err| {
38                ctx_err.cause().map_or_else(
39                    || {
40                        let mut labels = String::new();
41                        let mut msg = "expected ".to_string();
42
43                        for ctx in ctx_err.context() {
44                            match ctx {
45                                StrContext::Label(label) => {
46                                    labels += " <- ";
47                                    labels += label;
48                                }
49                                StrContext::Expected(expected) => {
50                                    msg += &expected.to_string();
51                                }
52                                _ => (),
53                            }
54                        }
55                        (labels, msg)
56                    },
57                    |cause| (String::new(), cause.to_string()),
58                )
59            })
60            .into_inner()
61            .unwrap_or_default();
62
63        let input = input.to_string();
64        let start = err_pos;
65        let end = (start + 1..)
66            .find(|e| input.is_char_boundary(*e))
67            .unwrap_or(start);
68
69        Self {
70            title: labels,
71            message,
72            span: start..end,
73            input,
74        }
75    }
76
77    pub fn from_display<T, U>(message: T, input: U, err_pos: usize) -> Self
78    where
79        T: core::fmt::Display,
80        U: core::fmt::Display,
81    {
82        let input = input.to_string();
83        let start = err_pos;
84        let end = (start + 1..)
85            .find(|e| input.is_char_boundary(*e))
86            .unwrap_or(start);
87
88        Self {
89            title: "Validation Error".to_string(),
90            message: message.to_string(),
91            span: start..end,
92            input,
93        }
94    }
95}
96
97impl fmt::Display for ReadableError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let message = annotate_snippets::Level::Error.title(&self.title).snippet(
100            annotate_snippets::Snippet::source(&self.input)
101                .fold(true)
102                .annotation(
103                    annotate_snippets::Level::Error
104                        .span(self.span.clone())
105                        .label(&self.message),
106                ),
107        );
108        let renderer = annotate_snippets::Renderer::plain();
109        let rendered = renderer.render(message);
110        rendered.fmt(f)
111    }
112}
113
114impl std::error::Error for ReadableError {}