mailparsing/
nom_utils.rs

1use nom::error::{ContextError, ErrorKind};
2use nom_locate::LocatedSpan;
3use std::fmt::{Debug, Write};
4
5pub(crate) type Span<'a> = LocatedSpan<&'a str>;
6pub(crate) type IResult<'a, A, B> = nom::IResult<A, B, ParseError<Span<'a>>>;
7
8pub fn make_span(s: &str) -> Span {
9    Span::new(s)
10}
11
12#[derive(Debug)]
13pub enum ParseErrorKind {
14    Context(&'static str),
15    Char(char),
16    Nom(ErrorKind),
17    External { kind: ErrorKind, reason: String },
18}
19
20#[derive(Debug)]
21pub struct ParseError<I: Debug> {
22    pub errors: Vec<(I, ParseErrorKind)>,
23}
24
25impl<I: Debug> ContextError<I> for ParseError<I> {
26    fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
27        other.errors.push((input, ParseErrorKind::Context(ctx)));
28        other
29    }
30}
31
32impl<I: Debug> nom::error::ParseError<I> for ParseError<I> {
33    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
34        Self {
35            errors: vec![(input, ParseErrorKind::Nom(kind))],
36        }
37    }
38
39    fn append(input: I, kind: ErrorKind, mut other: Self) -> Self {
40        other.errors.push((input, ParseErrorKind::Nom(kind)));
41        other
42    }
43
44    fn from_char(input: I, c: char) -> Self {
45        Self {
46            errors: vec![(input, ParseErrorKind::Char(c))],
47        }
48    }
49}
50
51impl<I: Debug, E: std::fmt::Display> nom::error::FromExternalError<I, E> for ParseError<I> {
52    fn from_external_error(input: I, kind: ErrorKind, err: E) -> Self {
53        Self {
54            errors: vec![(
55                input,
56                ParseErrorKind::External {
57                    kind,
58                    reason: format!("{err:#}"),
59                },
60            )],
61        }
62    }
63}
64
65pub fn make_context_error<S: Into<String>>(
66    input: Span<'_>,
67    reason: S,
68) -> nom::Err<ParseError<Span<'_>>> {
69    nom::Err::Error(ParseError {
70        errors: vec![(
71            input,
72            ParseErrorKind::External {
73                kind: nom::error::ErrorKind::Fail,
74                reason: reason.into(),
75            },
76        )],
77    })
78}
79
80pub fn explain_nom(input: Span, err: nom::Err<ParseError<Span<'_>>>) -> String {
81    match err {
82        nom::Err::Error(e) => {
83            let mut result = String::new();
84            for (i, (span, kind)) in e.errors.iter().enumerate() {
85                if input.is_empty() {
86                    match kind {
87                        ParseErrorKind::Char(c) => {
88                            write!(&mut result, "{i}: expected '{c}', got empty input\n\n")
89                        }
90                        ParseErrorKind::Context(s) => {
91                            write!(&mut result, "{i}: in {s}, got empty input\n\n")
92                        }
93                        ParseErrorKind::External { kind, reason } => {
94                            write!(&mut result, "{i}: {reason} {kind:?}, got empty input\n\n")
95                        }
96                        ParseErrorKind::Nom(e) => {
97                            write!(&mut result, "{i}: in {e:?}, got empty input\n\n")
98                        }
99                    }
100                    .ok();
101                    continue;
102                }
103
104                let line_number = span.location_line();
105                let line = std::str::from_utf8(span.get_line_beginning())
106                    .unwrap_or("<INVALID: line slice is not utf8!>");
107                // Remap \t in particular, because it can render as multiple
108                // columns and defeat the column number calculation provided
109                // by the Span type
110                let line: String = line
111                    .chars()
112                    .map(|c| match c {
113                        '\t' => '\u{2409}',
114                        '\r' => '\u{240d}',
115                        '\n' => '\u{240a}',
116                        _ => c,
117                    })
118                    .collect();
119                let column = span.get_utf8_column();
120                let mut caret = " ".repeat(column.saturating_sub(1));
121                caret.push('^');
122                for _ in 1..span.fragment().len() {
123                    caret.push('_')
124                }
125
126                match kind {
127                    ParseErrorKind::Char(expected) => {
128                        if let Some(actual) = span.fragment().chars().next() {
129                            write!(
130                                &mut result,
131                                "{i}: at line {line_number}:\n\
132                                    {line}\n\
133                                    {caret}\n\
134                                    expected '{expected}', found {actual}\n\n",
135                            )
136                        } else {
137                            write!(
138                                &mut result,
139                                "{i}: at line {line_number}:\n\
140                                    {line}\n\
141                                    {caret}\n\
142                                    expected '{expected}', got end of input\n\n",
143                            )
144                        }
145                    }
146                    ParseErrorKind::Context(context) => {
147                        write!(
148                            &mut result,
149                            "{i}: at line {line_number}, in {context}:\n\
150                                {line}\n\
151                                {caret}\n\n",
152                        )
153                    }
154                    ParseErrorKind::External { kind, reason } => {
155                        write!(
156                            &mut result,
157                            "{i}: at line {line_number}, {reason} {kind:?}:\n\
158                                {line}\n\
159                                {caret}\n\n",
160                        )
161                    }
162                    ParseErrorKind::Nom(nom_err) => {
163                        write!(
164                            &mut result,
165                            "{i}: at line {line_number}, in {nom_err:?}:\n\
166                                {line}\n\
167                                {caret}\n\n",
168                        )
169                    }
170                }
171                .ok();
172            }
173            result
174        }
175        _ => format!("{err:#}"),
176    }
177}