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::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
167#[cfg(feature = "std")]
168impl std::error::Error for Error {}
169#[cfg(not(feature = "std"))]
170#[cfg(feature = "serde")]
171impl serde::de::StdError for Error {}
172
173fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
174    if input.is_empty() {
175        return (0, index);
176    }
177
178    let safe_index = index.min(input.len() - 1);
179    let column_offset = index - safe_index;
180    let index = safe_index;
181
182    let nl = input[0..index]
183        .iter()
184        .rev()
185        .enumerate()
186        .find(|(_, b)| **b == b'\n')
187        .map(|(nl, _)| index - nl - 1);
188    let line_start = match nl {
189        Some(nl) => nl + 1,
190        None => 0,
191    };
192    let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
193
194    let column = core::str::from_utf8(&input[line_start..=index])
195        .map(|s| s.chars().count() - 1)
196        .unwrap_or_else(|_| index - line_start);
197    let column = column + column_offset;
198
199    (line, column)
200}
201
202#[cfg(feature = "parse")]
203pub(crate) struct TomlSink<'i, S> {
204    source: toml_parser::Source<'i>,
205    input: Option<alloc::sync::Arc<str>>,
206    sink: S,
207}
208
209#[cfg(feature = "parse")]
210impl<'i, S: Default> TomlSink<'i, S> {
211    pub(crate) fn new(source: toml_parser::Source<'i>) -> Self {
212        Self {
213            source,
214            input: None,
215            sink: Default::default(),
216        }
217    }
218
219    pub(crate) fn into_inner(self) -> S {
220        self.sink
221    }
222}
223
224#[cfg(feature = "parse")]
225impl<'i> toml_parser::ErrorSink for TomlSink<'i, Option<Error>> {
226    fn report_error(&mut self, error: toml_parser::ParseError) {
227        if self.sink.is_none() {
228            let input = self
229                .input
230                .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
231            let error = Error::new(input.clone(), error);
232            self.sink = Some(error);
233        }
234    }
235}
236
237#[cfg(feature = "parse")]
238impl<'i> toml_parser::ErrorSink for TomlSink<'i, Vec<Error>> {
239    fn report_error(&mut self, error: toml_parser::ParseError) {
240        let input = self
241            .input
242            .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
243        let error = Error::new(input.clone(), error);
244        self.sink.push(error);
245    }
246}
247
248#[cfg(test)]
249mod test_translate_position {
250    use super::*;
251
252    #[test]
253    fn empty() {
254        let input = b"";
255        let index = 0;
256        let position = translate_position(&input[..], index);
257        assert_eq!(position, (0, 0));
258    }
259
260    #[test]
261    fn start() {
262        let input = b"Hello";
263        let index = 0;
264        let position = translate_position(&input[..], index);
265        assert_eq!(position, (0, 0));
266    }
267
268    #[test]
269    fn end() {
270        let input = b"Hello";
271        let index = input.len() - 1;
272        let position = translate_position(&input[..], index);
273        assert_eq!(position, (0, input.len() - 1));
274    }
275
276    #[test]
277    fn after() {
278        let input = b"Hello";
279        let index = input.len();
280        let position = translate_position(&input[..], index);
281        assert_eq!(position, (0, input.len()));
282    }
283
284    #[test]
285    fn first_line() {
286        let input = b"Hello\nWorld\n";
287        let index = 2;
288        let position = translate_position(&input[..], index);
289        assert_eq!(position, (0, 2));
290    }
291
292    #[test]
293    fn end_of_line() {
294        let input = b"Hello\nWorld\n";
295        let index = 5;
296        let position = translate_position(&input[..], index);
297        assert_eq!(position, (0, 5));
298    }
299
300    #[test]
301    fn start_of_second_line() {
302        let input = b"Hello\nWorld\n";
303        let index = 6;
304        let position = translate_position(&input[..], index);
305        assert_eq!(position, (1, 0));
306    }
307
308    #[test]
309    fn second_line() {
310        let input = b"Hello\nWorld\n";
311        let index = 8;
312        let position = translate_position(&input[..], index);
313        assert_eq!(position, (1, 2));
314    }
315}