1use std::error::Error as StdError;
2use std::fmt::{Display, Formatter, Result};
3
4use crate::parser::prelude::*;
5use crate::Key;
6
7use winnow::error::ContextError;
8use winnow::error::ParseError;
9
10/// Type representing a TOML parse error
11#[derive(Debug, Clone, Eq, PartialEq, Hash)]
12pub struct TomlError {
13 message: String,
14 original: Option<String>,
15 keys: Vec<String>,
16 span: Option<std::ops::Range<usize>>,
17}
18
19impl 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
91impl 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
143impl StdError for TomlError {
144 fn description(&self) -> &'static str {
145 "TOML parse error"
146 }
147}
148
149fn 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)]
179mod 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)]
248pub(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
262impl 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
282impl StdError for CustomError {
283 fn description(&self) -> &'static str {
284 "TOML parse error"
285 }
286}
287
288impl 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