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