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