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 let line = line;
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)]
180mod test_translate_position {
181 use super::*;
182
183 #[test]
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]
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]
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]
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]
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]
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]
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]
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
248#[derive(Debug, Clone)]
249pub(crate) enum CustomError {
250 DuplicateKey {
251 key: String,
252 table: Option<Vec<Key>>,
253 },
254 DottedKeyExtendWrongType {
255 key: Vec<Key>,
256 actual: &'static str,
257 },
258 OutOfRange,
259 #[cfg_attr(feature = "unbounded", allow(dead_code))]
260 RecursionLimitExceeded,
261}
262
263impl CustomError {
264 pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self {
265 assert!(i < path.len());
266 let key: &Key = &path[i];
267 let repr: Cow<'_, str> = key.display_repr();
268 Self::DuplicateKey {
269 key: repr.into(),
270 table: Some(path[..i].to_vec()),
271 }
272 }
273
274 pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self {
275 assert!(i < path.len());
276 Self::DottedKeyExtendWrongType {
277 key: path[..=i].to_vec(),
278 actual,
279 }
280 }
281}
282
283impl StdError for CustomError {
284 fn description(&self) -> &'static str {
285 "TOML parse error"
286 }
287}
288
289impl Display for CustomError {
290 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
291 match self {
292 CustomError::DuplicateKey { key, table } => {
293 if let Some(table) = table {
294 if table.is_empty() {
295 write!(f, "duplicate key `{}` in document root", key)
296 } else {
297 let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
298 write!(f, "duplicate key `{}` in table `{}`", key, path)
299 }
300 } else {
301 write!(f, "duplicate key `{}`", key)
302 }
303 }
304 CustomError::DottedKeyExtendWrongType { key, actual } => {
305 let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
306 write!(
307 f,
308 "dotted key `{}` attempted to extend non-table type ({})",
309 path, actual
310 )
311 }
312 CustomError::OutOfRange => write!(f, "value is out of range"),
313 CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceeded"),
314 }
315 }
316}
317