1#[derive(Debug, Clone, Eq, PartialEq, Hash)]
3pub struct TomlError {
4 message: String,
5 input: Option<std::sync::Arc<str>>,
6 keys: Vec<String>,
7 span: Option<std::ops::Range<usize>>,
8}
9
10impl TomlError {
11 #[cfg(feature = "parse")]
12 pub(crate) fn new(input: std::sync::Arc<str>, error: toml_parser::ParseError) -> Self {
13 let mut message = String::new();
14 message.push_str(error.description());
15 if let Some(expected) = error.expected() {
16 message.push_str(", expected ");
17 if expected.is_empty() {
18 message.push_str("nothing");
19 } else {
20 for (i, expected) in expected.iter().enumerate() {
21 if i != 0 {
22 message.push_str(", ");
23 }
24 match expected {
25 toml_parser::Expected::Literal(desc) => {
26 message.push_str(&render_literal(desc));
27 }
28 toml_parser::Expected::Description(desc) => message.push_str(desc),
29 _ => message.push_str("etc"),
30 }
31 }
32 }
33 }
34
35 let span = error.unexpected().map(|span| span.start()..span.end());
36
37 Self {
38 message,
39 input: Some(input),
40 keys: Vec::new(),
41 span,
42 }
43 }
44
45 #[cfg(feature = "serde")]
46 pub(crate) fn custom(message: String, span: Option<std::ops::Range<usize>>) -> Self {
47 Self {
48 message,
49 input: None,
50 keys: Vec::new(),
51 span,
52 }
53 }
54
55 #[cfg(feature = "serde")]
56 pub(crate) fn add_key(&mut self, key: String) {
57 self.keys.insert(0, key);
58 }
59
60 pub fn message(&self) -> &str {
62 &self.message
63 }
64
65 pub fn span(&self) -> Option<std::ops::Range<usize>> {
67 self.span.clone()
68 }
69
70 #[cfg(feature = "serde")]
71 pub(crate) fn set_span(&mut self, span: Option<std::ops::Range<usize>>) {
72 self.span = span;
73 }
74
75 #[cfg(feature = "serde")]
76 pub(crate) fn set_input(&mut self, input: Option<&str>) {
77 self.input = input.map(|s| s.into());
78 }
79}
80
81fn render_literal(literal: &str) -> String {
82 match literal {
83 "\n" => "newline".to_owned(),
84 "`" => "'`'".to_owned(),
85 s if s.chars().all(|c| c.is_ascii_control()) => {
86 format!("`{}`", s.escape_debug())
87 }
88 s => format!("`{s}`"),
89 }
90}
91
92impl std::fmt::Display for TomlError {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 let mut context = false;
107 if let (Some(input), Some(span)) = (&self.input, self.span()) {
108 context = true;
109
110 let (line, column) = translate_position(input.as_bytes(), span.start);
111 let line_num = line + 1;
112 let col_num = column + 1;
113 let gutter = line_num.to_string().len();
114 let content = input.split('\n').nth(line).expect("valid line number");
115 let highlight_len = span.end - span.start;
116 let highlight_len = highlight_len.min(content.len().saturating_sub(column));
118
119 writeln!(f, "TOML parse error at line {line_num}, column {col_num}")?;
120 for _ in 0..=gutter {
122 write!(f, " ")?;
123 }
124 writeln!(f, "|")?;
125
126 write!(f, "{line_num} | ")?;
128 writeln!(f, "{content}")?;
129
130 for _ in 0..=gutter {
132 write!(f, " ")?;
133 }
134 write!(f, "|")?;
135 for _ in 0..=column {
136 write!(f, " ")?;
137 }
138 write!(f, "^")?;
141 for _ in 1..highlight_len {
142 write!(f, "^")?;
143 }
144 writeln!(f)?;
145 }
146 writeln!(f, "{}", self.message)?;
147 if !context && !self.keys.is_empty() {
148 writeln!(f, "in `{}`", self.keys.join("."))?;
149 }
150
151 Ok(())
152 }
153}
154
155impl std::error::Error for TomlError {}
156
157fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
158 if input.is_empty() {
159 return (0, index);
160 }
161
162 let safe_index = index.min(input.len() - 1);
163 let column_offset = index - safe_index;
164 let index = safe_index;
165
166 let nl = input[0..index]
167 .iter()
168 .rev()
169 .enumerate()
170 .find(|(_, b)| **b == b'\n')
171 .map(|(nl, _)| index - nl - 1);
172 let line_start = match nl {
173 Some(nl) => nl + 1,
174 None => 0,
175 };
176 let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
177
178 let column = std::str::from_utf8(&input[line_start..=index])
179 .map(|s| s.chars().count() - 1)
180 .unwrap_or_else(|_| index - line_start);
181 let column = column + column_offset;
182
183 (line, column)
184}
185
186#[cfg(feature = "parse")]
187pub(crate) struct TomlSink<'i, S> {
188 source: toml_parser::Source<'i>,
189 input: Option<std::sync::Arc<str>>,
190 sink: S,
191}
192
193#[cfg(feature = "parse")]
194impl<'i, S: Default> TomlSink<'i, S> {
195 pub(crate) fn new(source: toml_parser::Source<'i>) -> Self {
196 Self {
197 source,
198 input: None,
199 sink: Default::default(),
200 }
201 }
202
203 pub(crate) fn into_inner(self) -> S {
204 self.sink
205 }
206}
207
208#[cfg(feature = "parse")]
209impl<'i> toml_parser::ErrorSink for TomlSink<'i, Option<TomlError>> {
210 fn report_error(&mut self, error: toml_parser::ParseError) {
211 if self.sink.is_none() {
212 let input = self
213 .input
214 .get_or_insert_with(|| std::sync::Arc::from(self.source.input()));
215 let error = TomlError::new(input.clone(), error);
216 self.sink = Some(error);
217 }
218 }
219}
220
221#[cfg(feature = "parse")]
222impl<'i> toml_parser::ErrorSink for TomlSink<'i, Vec<TomlError>> {
223 fn report_error(&mut self, error: toml_parser::ParseError) {
224 let input = self
225 .input
226 .get_or_insert_with(|| std::sync::Arc::from(self.source.input()));
227 let error = TomlError::new(input.clone(), error);
228 self.sink.push(error);
229 }
230}
231
232#[cfg(test)]
233mod test_translate_position {
234 use super::*;
235
236 #[test]
237 fn empty() {
238 let input = b"";
239 let index = 0;
240 let position = translate_position(&input[..], index);
241 assert_eq!(position, (0, 0));
242 }
243
244 #[test]
245 fn start() {
246 let input = b"Hello";
247 let index = 0;
248 let position = translate_position(&input[..], index);
249 assert_eq!(position, (0, 0));
250 }
251
252 #[test]
253 fn end() {
254 let input = b"Hello";
255 let index = input.len() - 1;
256 let position = translate_position(&input[..], index);
257 assert_eq!(position, (0, input.len() - 1));
258 }
259
260 #[test]
261 fn after() {
262 let input = b"Hello";
263 let index = input.len();
264 let position = translate_position(&input[..], index);
265 assert_eq!(position, (0, input.len()));
266 }
267
268 #[test]
269 fn first_line() {
270 let input = b"Hello\nWorld\n";
271 let index = 2;
272 let position = translate_position(&input[..], index);
273 assert_eq!(position, (0, 2));
274 }
275
276 #[test]
277 fn end_of_line() {
278 let input = b"Hello\nWorld\n";
279 let index = 5;
280 let position = translate_position(&input[..], index);
281 assert_eq!(position, (0, 5));
282 }
283
284 #[test]
285 fn start_of_second_line() {
286 let input = b"Hello\nWorld\n";
287 let index = 6;
288 let position = translate_position(&input[..], index);
289 assert_eq!(position, (1, 0));
290 }
291
292 #[test]
293 fn second_line() {
294 let input = b"Hello\nWorld\n";
295 let index = 8;
296 let position = translate_position(&input[..], index);
297 assert_eq!(position, (1, 2));
298 }
299}