Skip to main content

toml/de/
error.rs

1use crate::alloc_prelude::*;
2
3/// Errors that can occur when deserializing a type.
4#[derive(Debug, Clone, Eq, PartialEq, Hash)]
5pub struct Error {
6    message: String,
7    input: Option<alloc::sync::Arc<str>>,
8    keys: Vec<String>,
9    span: Option<core::ops::Range<usize>>,
10}
11
12impl Error {
13    #[cfg(feature = "parse")]
14    pub(crate) fn new(input: alloc::sync::Arc<str>, error: toml_parser::ParseError) -> Self {
15        let mut message = String::new();
16        message.push_str(error.description());
17        if let Some(expected) = error.expected() {
18            message.push_str(", expected ");
19            if expected.is_empty() {
20                message.push_str("nothing");
21            } else {
22                for (i, expected) in expected.iter().enumerate() {
23                    if i != 0 {
24                        message.push_str(", ");
25                    }
26                    match expected {
27                        toml_parser::Expected::Literal(desc) => {
28                            message.push_str(&render_literal(desc));
29                        }
30                        toml_parser::Expected::Description(desc) => message.push_str(desc),
31                        _ => message.push_str("etc"),
32                    }
33                }
34            }
35        }
36
37        let span = error.unexpected().map(|span| span.start()..span.end());
38
39        Self {
40            message,
41            input: Some(input),
42            keys: Vec::new(),
43            span,
44        }
45    }
46
47    pub(crate) fn custom<T>(msg: T, span: Option<core::ops::Range<usize>>) -> Self
48    where
49        T: core::fmt::Display,
50    {
51        Self {
52            message: msg.to_string(),
53            input: None,
54            keys: Vec::new(),
55            span,
56        }
57    }
58
59    pub(crate) fn add_key(&mut self, key: String) {
60        self.keys.insert(0, key);
61    }
62
63    /// What went wrong
64    pub fn message(&self) -> &str {
65        &self.message
66    }
67
68    /// The start/end index into the original document where the error occurred
69    pub fn span(&self) -> Option<core::ops::Range<usize>> {
70        self.span.clone()
71    }
72
73    pub(crate) fn set_span(&mut self, span: Option<core::ops::Range<usize>>) {
74        self.span = span;
75    }
76
77    /// Provide the encoded TOML the error applies to
78    pub fn set_input(&mut self, input: Option<&str>) {
79        self.input = input.map(|s| s.into());
80    }
81}
82
83#[cfg(feature = "serde")]
84impl serde_core::de::Error for Error {
85    fn custom<T>(msg: T) -> Self
86    where
87        T: core::fmt::Display,
88    {
89        Self::custom(msg.to_string(), None)
90    }
91}
92
93fn render_literal(literal: &str) -> String {
94    match literal {
95        "\n" => "newline".to_owned(),
96        "`" => "'`'".to_owned(),
97        s if s.chars().all(|c| c.is_ascii_control()) => {
98            format!("`{}`", s.escape_debug())
99        }
100        s => format!("`{s}`"),
101    }
102}
103
104/// Displays a TOML parse error
105///
106/// # Example
107///
108/// TOML parse error at line 1, column 10
109///   |
110/// 1 | 00:32:00.a999999
111///   |          ^
112/// Unexpected `a`
113/// Expected `digit`
114/// While parsing a Time
115/// While parsing a Date-Time
116impl core::fmt::Display for Error {
117    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118        let mut context = false;
119        if let (Some(input), Some(span)) = (&self.input, self.span()) {
120            context = true;
121
122            let (line, column) = translate_position(input.as_bytes(), span.start);
123            let line_num = line + 1;
124            let col_num = column + 1;
125            let gutter = line_num.to_string().len();
126            let content = input.split('\n').nth(line).expect("valid line number");
127            let highlight_len = span.end - span.start;
128            // Allow highlight to go one past the line
129            let highlight_len = highlight_len.min(content.len().saturating_sub(column));
130
131            writeln!(f, "TOML parse error at line {line_num}, column {col_num}")?;
132            //   |
133            for _ in 0..=gutter {
134                write!(f, " ")?;
135            }
136            writeln!(f, "|")?;
137
138            // 1 | 00:32:00.a999999
139            write!(f, "{line_num} | ")?;
140            writeln!(f, "{content}")?;
141
142            //   |          ^
143            for _ in 0..=gutter {
144                write!(f, " ")?;
145            }
146            write!(f, "|")?;
147            for _ in 0..=column {
148                write!(f, " ")?;
149            }
150            // The span will be empty at eof, so we need to make sure we always print at least
151            // one `^`
152            write!(f, "^")?;
153            for _ in 1..highlight_len {
154                write!(f, "^")?;
155            }
156            writeln!(f)?;
157        }
158        writeln!(f, "{}", self.message)?;
159        if !context && !self.keys.is_empty() {
160            writeln!(f, "in `{}`", self.keys.join("."))?;
161        }
162
163        Ok(())
164    }
165}
166
167impl core::error::Error for Error {}
168
169fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
170    if input.is_empty() {
171        return (0, index);
172    }
173
174    let safe_index = index.min(input.len() - 1);
175    let column_offset = index - safe_index;
176    let index = safe_index;
177
178    let nl = input[0..index]
179        .iter()
180        .rev()
181        .enumerate()
182        .find(|(_, b)| **b == b'\n')
183        .map(|(nl, _)| index - nl - 1);
184    let line_start = match nl {
185        Some(nl) => nl + 1,
186        None => 0,
187    };
188    let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
189
190    let column = core::str::from_utf8(&input[line_start..=index])
191        .map(|s| s.chars().count() - 1)
192        .unwrap_or_else(|_| index - line_start);
193    let column = column + column_offset;
194
195    (line, column)
196}
197
198#[cfg(feature = "parse")]
199pub(crate) struct TomlSink<'i, S> {
200    source: toml_parser::Source<'i>,
201    input: Option<alloc::sync::Arc<str>>,
202    sink: S,
203}
204
205#[cfg(feature = "parse")]
206impl<'i, S: Default> TomlSink<'i, S> {
207    pub(crate) fn new(source: toml_parser::Source<'i>) -> Self {
208        Self {
209            source,
210            input: None,
211            sink: Default::default(),
212        }
213    }
214
215    pub(crate) fn into_inner(self) -> S {
216        self.sink
217    }
218}
219
220#[cfg(feature = "parse")]
221impl<'i> toml_parser::ErrorSink for TomlSink<'i, Option<Error>> {
222    fn report_error(&mut self, error: toml_parser::ParseError) {
223        if self.sink.is_none() {
224            let input = self
225                .input
226                .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
227            let error = Error::new(input.clone(), error);
228            self.sink = Some(error);
229        }
230    }
231}
232
233#[cfg(feature = "parse")]
234impl<'i> toml_parser::ErrorSink for TomlSink<'i, Vec<Error>> {
235    fn report_error(&mut self, error: toml_parser::ParseError) {
236        let input = self
237            .input
238            .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
239        let error = Error::new(input.clone(), error);
240        self.sink.push(error);
241    }
242}
243
244#[cfg(test)]
245mod test_translate_position {
246    use super::*;
247
248    #[test]
249    fn empty() {
250        let input = b"";
251        let index = 0;
252        let position = translate_position(&input[..], index);
253        assert_eq!(position, (0, 0));
254    }
255
256    #[test]
257    fn start() {
258        let input = b"Hello";
259        let index = 0;
260        let position = translate_position(&input[..], index);
261        assert_eq!(position, (0, 0));
262    }
263
264    #[test]
265    fn end() {
266        let input = b"Hello";
267        let index = input.len() - 1;
268        let position = translate_position(&input[..], index);
269        assert_eq!(position, (0, input.len() - 1));
270    }
271
272    #[test]
273    fn after() {
274        let input = b"Hello";
275        let index = input.len();
276        let position = translate_position(&input[..], index);
277        assert_eq!(position, (0, input.len()));
278    }
279
280    #[test]
281    fn first_line() {
282        let input = b"Hello\nWorld\n";
283        let index = 2;
284        let position = translate_position(&input[..], index);
285        assert_eq!(position, (0, 2));
286    }
287
288    #[test]
289    fn end_of_line() {
290        let input = b"Hello\nWorld\n";
291        let index = 5;
292        let position = translate_position(&input[..], index);
293        assert_eq!(position, (0, 5));
294    }
295
296    #[test]
297    fn start_of_second_line() {
298        let input = b"Hello\nWorld\n";
299        let index = 6;
300        let position = translate_position(&input[..], index);
301        assert_eq!(position, (1, 0));
302    }
303
304    #[test]
305    fn second_line() {
306        let input = b"Hello\nWorld\n";
307        let index = 8;
308        let position = translate_position(&input[..], index);
309        assert_eq!(position, (1, 2));
310    }
311}