parse_mediawiki_sql/
error.rs

1/*!
2The error types used by [`FromSqlTuple`](crate::FromSqlTuple) and [`FromSql`](crate::from_sql::FromSql).
3*/
4
5use bstr::{BStr, ByteSlice};
6use joinery::prelude::*;
7use nom::{
8    branch::alt,
9    bytes::streaming::tag,
10    character::streaming::char,
11    combinator::{opt, recognize},
12    error::{ContextError, ErrorKind, FromExternalError, ParseError},
13    multi::many1,
14    sequence::{delimited, pair},
15};
16
17use std::fmt::Display;
18
19use crate::from_sql::FromSql;
20
21/// Used inside [`Error`] to store the names of the items that were being parsed.
22#[derive(Debug, Clone, Eq, PartialEq)]
23pub enum ParseTypeContext<'a> {
24    Single {
25        input: &'a BStr,
26        label: &'static str,
27    },
28    Alternatives {
29        input: &'a BStr,
30        labels: Vec<&'static str>,
31    },
32}
33
34impl<'a> ParseTypeContext<'a> {
35    fn push(&mut self, other: Self) {
36        match self {
37            ParseTypeContext::Single { label: label1, .. } => match other {
38                ParseTypeContext::Single {
39                    input,
40                    label: label2,
41                } => {
42                    *self = ParseTypeContext::Alternatives {
43                        input,
44                        labels: vec![label1, label2],
45                    }
46                }
47                ParseTypeContext::Alternatives {
48                    input,
49                    labels: mut labels2,
50                } => {
51                    labels2.insert(0, label1);
52                    *self = ParseTypeContext::Alternatives {
53                        input,
54                        labels: labels2,
55                    }
56                }
57            },
58            ParseTypeContext::Alternatives {
59                labels: labels1, ..
60            } => match other {
61                ParseTypeContext::Single { label: label2, .. } => {
62                    labels1.push(label2);
63                }
64                ParseTypeContext::Alternatives {
65                    labels: labels2, ..
66                } => {
67                    labels1.extend(labels2);
68                }
69            },
70        }
71    }
72}
73
74/// Error type used by [`FromSql`](crate::from_sql::FromSql).
75///
76/// Keeps a list of the items that were being parsed when an error was encountered.
77/// The [`Display`] implementation prints a backtrace with a snippet of the text that failed to parse.
78#[derive(Debug, Clone, Eq, PartialEq)]
79pub enum Error<'a> {
80    ErrorKind { input: &'a BStr, kind: ErrorKind },
81    ErrorWithContexts(Vec<ParseTypeContext<'a>>),
82}
83
84impl<'a> ParseError<&'a [u8]> for Error<'a> {
85    fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self {
86        Self::ErrorKind {
87            input: input.into(),
88            kind,
89        }
90    }
91
92    // Bubble up ErrorWithContext and skip ErrorKind.
93    fn append(input: &'a [u8], kind: ErrorKind, other: Self) -> Self {
94        match other {
95            Self::ErrorKind { .. } => Self::from_error_kind(input, kind),
96            e @ Self::ErrorWithContexts(_) => e,
97        }
98    }
99
100    fn from_char(input: &'a [u8], _: char) -> Self {
101        Self::from_error_kind(input, ErrorKind::Char)
102    }
103
104    fn or(self, other: Self) -> Self {
105        match self {
106            Error::ErrorKind { .. } => match other {
107                Error::ErrorKind { input, kind } => Self::from_error_kind(input, kind),
108                e @ Error::ErrorWithContexts(_) => e,
109            },
110            Error::ErrorWithContexts(mut contexts) => match other {
111                Error::ErrorKind { .. } => Error::ErrorWithContexts(contexts),
112                Error::ErrorWithContexts(mut other_contexts) => {
113                    if let (Some(mut old_context), Some(new_context)) =
114                        (contexts.pop(), other_contexts.pop())
115                    {
116                        old_context.push(new_context);
117                        other_contexts.push(old_context);
118                    };
119                    Error::ErrorWithContexts(other_contexts)
120                }
121            },
122        }
123    }
124}
125
126impl<'a> ContextError<&'a [u8]> for Error<'a> {
127    fn add_context(input: &'a [u8], label: &'static str, other: Self) -> Self {
128        let context = ParseTypeContext::Single {
129            input: input.into(),
130            label,
131        };
132        match other {
133            Self::ErrorKind { .. } => Self::ErrorWithContexts(vec![context]),
134            Self::ErrorWithContexts(mut contexts) => {
135                contexts.push(context);
136                Self::ErrorWithContexts(contexts)
137            }
138        }
139    }
140}
141
142impl<'a, I: Into<&'a [u8]>, E> FromExternalError<I, E> for Error<'a> {
143    fn from_external_error(input: I, kind: ErrorKind, _e: E) -> Self {
144        Self::from_error_kind(input.into(), kind)
145    }
146}
147
148const INPUT_GRAPHEMES_TO_SHOW: usize = 100;
149
150impl<'a> Display for Error<'a> {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        fn show_input(input: &BStr) -> &BStr {
153            if input.is_empty() {
154                return input;
155            }
156            // Try to get a whole SQL tuple.
157            if input[0] == b'(' {
158                if let Ok((_, row)) = recognize(delimited(
159                    char('('),
160                    many1(pair(
161                        alt((
162                            tag("NULL"),
163                            recognize(f64::from_sql),
164                            recognize(i64::from_sql),
165                            recognize(<Vec<u8>>::from_sql),
166                        )),
167                        opt(char(',')),
168                    )),
169                    char(')'),
170                ))(input)
171                {
172                    return row.into();
173                }
174            }
175            // Try to get one element of the SQL tuple.
176            if let Ok((_, result)) = alt((
177                tag("NULL"),
178                recognize(f64::from_sql),
179                recognize(i64::from_sql),
180                recognize(<Vec<u8>>::from_sql),
181            ))(input)
182            {
183                result.into()
184            // Get up to a maximum number of characters.
185            } else {
186                let (_, end, _) = input
187                    .grapheme_indices()
188                    .take(INPUT_GRAPHEMES_TO_SHOW)
189                    .last()
190                    .expect("we have checked that input is not empty");
191                &input[..end]
192            }
193        }
194
195        match self {
196            Error::ErrorKind { input, kind } => write!(
197                f,
198                "error in {} combinator at\n\t{}",
199                kind.description(),
200                show_input(input),
201            ),
202            Error::ErrorWithContexts(contexts) => {
203                match contexts.as_slice() {
204                    [] => {
205                        write!(f, "unknown error")?;
206                    }
207                    [first, rest @ ..] => {
208                        let mut last_input = match first {
209                            ParseTypeContext::Single { input, label } => {
210                                write!(f, "expected {} at\n\t{}\n", label, show_input(input),)?;
211                                input
212                            }
213                            ParseTypeContext::Alternatives { input, labels } => {
214                                write!(
215                                    f,
216                                    "expected {} at \n\t{}\n",
217                                    labels.iter().join_with(" or "),
218                                    show_input(input),
219                                )?;
220                                input
221                            }
222                        };
223                        for context in rest {
224                            let labels_joined;
225                            let (displayed_label, input): (&dyn Display, _) = match context {
226                                ParseTypeContext::Single { input, label } => {
227                                    let displayed_input = if last_input == input {
228                                        None
229                                    } else {
230                                        Some(input)
231                                    };
232                                    last_input = input;
233                                    (label, displayed_input)
234                                }
235                                ParseTypeContext::Alternatives { input, labels } => {
236                                    let displayed_input = if last_input == input {
237                                        None
238                                    } else {
239                                        Some(input)
240                                    };
241                                    labels_joined = labels.iter().join_with(" or ");
242                                    last_input = input;
243                                    (&labels_joined, displayed_input)
244                                }
245                            };
246                            write!(f, "while parsing {}", displayed_label,)?;
247                            if let Some(input) = input {
248                                write!(f, " at\n\t{}", show_input(input),)?;
249                            }
250                            writeln!(f)?;
251                        }
252                    }
253                }
254                Ok(())
255            }
256        }
257    }
258}