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