1 use std::error::Error as StdError;
2 use std::fmt::{Display, Formatter, Result};
3 
4 /// Type representing a TOML parse error
5 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
6 pub struct TomlError {
7     message: String,
8     original: Option<String>,
9     keys: Vec<String>,
10     span: Option<std::ops::Range<usize>>,
11 }
12 
13 impl TomlError {
14     #[cfg(feature = "parse")]
new( error: winnow::error::ParseError< crate::parser::prelude::Input<'_>, winnow::error::ContextError, >, mut original: crate::parser::prelude::Input<'_>, ) -> Self15     pub(crate) fn new(
16         error: winnow::error::ParseError<
17             crate::parser::prelude::Input<'_>,
18             winnow::error::ContextError,
19         >,
20         mut original: crate::parser::prelude::Input<'_>,
21     ) -> Self {
22         use winnow::stream::Stream;
23 
24         let offset = error.offset();
25         let span = if offset == original.len() {
26             offset..offset
27         } else {
28             offset..(offset + 1)
29         };
30 
31         let message = error.inner().to_string();
32         let original = original.finish();
33 
34         Self {
35             message,
36             original: Some(
37                 String::from_utf8(original.to_owned()).expect("original document was utf8"),
38             ),
39             keys: Vec::new(),
40             span: Some(span),
41         }
42     }
43 
44     #[cfg(feature = "serde")]
custom(message: String, span: Option<std::ops::Range<usize>>) -> Self45     pub(crate) fn custom(message: String, span: Option<std::ops::Range<usize>>) -> Self {
46         Self {
47             message,
48             original: None,
49             keys: Vec::new(),
50             span,
51         }
52     }
53 
54     #[cfg(feature = "serde")]
add_key(&mut self, key: String)55     pub(crate) fn add_key(&mut self, key: String) {
56         self.keys.insert(0, key);
57     }
58 
59     /// What went wrong
message(&self) -> &str60     pub fn message(&self) -> &str {
61         &self.message
62     }
63 
64     /// The start/end index into the original document where the error occurred
span(&self) -> Option<std::ops::Range<usize>>65     pub fn span(&self) -> Option<std::ops::Range<usize>> {
66         self.span.clone()
67     }
68 
69     #[cfg(feature = "serde")]
set_span(&mut self, span: Option<std::ops::Range<usize>>)70     pub(crate) fn set_span(&mut self, span: Option<std::ops::Range<usize>>) {
71         self.span = span;
72     }
73 
74     #[cfg(feature = "serde")]
set_original(&mut self, original: Option<String>)75     pub(crate) fn set_original(&mut self, original: Option<String>) {
76         self.original = original;
77     }
78 }
79 
80 /// Displays a TOML parse error
81 ///
82 /// # Example
83 ///
84 /// TOML parse error at line 1, column 10
85 ///   |
86 /// 1 | 00:32:00.a999999
87 ///   |          ^
88 /// Unexpected `a`
89 /// Expected `digit`
90 /// While parsing a Time
91 /// While parsing a Date-Time
92 impl Display for TomlError {
fmt(&self, f: &mut Formatter<'_>) -> Result93     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
94         let mut context = false;
95         if let (Some(original), Some(span)) = (&self.original, self.span()) {
96             context = true;
97 
98             let (line, column) = translate_position(original.as_bytes(), span.start);
99             let line_num = line + 1;
100             let col_num = column + 1;
101             let gutter = line_num.to_string().len();
102             let content = original.split('\n').nth(line).expect("valid line number");
103 
104             writeln!(
105                 f,
106                 "TOML parse error at line {}, column {}",
107                 line_num, col_num
108             )?;
109             //   |
110             for _ in 0..=gutter {
111                 write!(f, " ")?;
112             }
113             writeln!(f, "|")?;
114 
115             // 1 | 00:32:00.a999999
116             write!(f, "{} | ", line_num)?;
117             writeln!(f, "{}", content)?;
118 
119             //   |          ^
120             for _ in 0..=gutter {
121                 write!(f, " ")?;
122             }
123             write!(f, "|")?;
124             for _ in 0..=column {
125                 write!(f, " ")?;
126             }
127             // The span will be empty at eof, so we need to make sure we always print at least
128             // one `^`
129             write!(f, "^")?;
130             for _ in (span.start + 1)..(span.end.min(span.start + content.len())) {
131                 write!(f, "^")?;
132             }
133             writeln!(f)?;
134         }
135         writeln!(f, "{}", self.message)?;
136         if !context && !self.keys.is_empty() {
137             writeln!(f, "in `{}`", self.keys.join("."))?;
138         }
139 
140         Ok(())
141     }
142 }
143 
144 impl StdError for TomlError {
description(&self) -> &'static str145     fn description(&self) -> &'static str {
146         "TOML parse error"
147     }
148 }
149 
translate_position(input: &[u8], index: usize) -> (usize, usize)150 fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
151     if input.is_empty() {
152         return (0, index);
153     }
154 
155     let safe_index = index.min(input.len() - 1);
156     let column_offset = index - safe_index;
157     let index = safe_index;
158 
159     let nl = input[0..index]
160         .iter()
161         .rev()
162         .enumerate()
163         .find(|(_, b)| **b == b'\n')
164         .map(|(nl, _)| index - nl - 1);
165     let line_start = match nl {
166         Some(nl) => nl + 1,
167         None => 0,
168     };
169     let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
170 
171     let column = std::str::from_utf8(&input[line_start..=index])
172         .map(|s| s.chars().count() - 1)
173         .unwrap_or_else(|_| index - line_start);
174     let column = column + column_offset;
175 
176     (line, column)
177 }
178 
179 #[cfg(test)]
180 mod test_translate_position {
181     use super::*;
182 
183     #[test]
empty()184     fn empty() {
185         let input = b"";
186         let index = 0;
187         let position = translate_position(&input[..], index);
188         assert_eq!(position, (0, 0));
189     }
190 
191     #[test]
start()192     fn start() {
193         let input = b"Hello";
194         let index = 0;
195         let position = translate_position(&input[..], index);
196         assert_eq!(position, (0, 0));
197     }
198 
199     #[test]
end()200     fn end() {
201         let input = b"Hello";
202         let index = input.len() - 1;
203         let position = translate_position(&input[..], index);
204         assert_eq!(position, (0, input.len() - 1));
205     }
206 
207     #[test]
after()208     fn after() {
209         let input = b"Hello";
210         let index = input.len();
211         let position = translate_position(&input[..], index);
212         assert_eq!(position, (0, input.len()));
213     }
214 
215     #[test]
first_line()216     fn first_line() {
217         let input = b"Hello\nWorld\n";
218         let index = 2;
219         let position = translate_position(&input[..], index);
220         assert_eq!(position, (0, 2));
221     }
222 
223     #[test]
end_of_line()224     fn end_of_line() {
225         let input = b"Hello\nWorld\n";
226         let index = 5;
227         let position = translate_position(&input[..], index);
228         assert_eq!(position, (0, 5));
229     }
230 
231     #[test]
start_of_second_line()232     fn start_of_second_line() {
233         let input = b"Hello\nWorld\n";
234         let index = 6;
235         let position = translate_position(&input[..], index);
236         assert_eq!(position, (1, 0));
237     }
238 
239     #[test]
second_line()240     fn second_line() {
241         let input = b"Hello\nWorld\n";
242         let index = 8;
243         let position = translate_position(&input[..], index);
244         assert_eq!(position, (1, 2));
245     }
246 }
247