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::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
167#[cfg(feature = "std")]
168impl std::error::Error for Error {}
169#[cfg(not(feature = "std"))]
170#[cfg(feature = "serde")]
171impl serde::de::StdError for Error {}
172
173fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
174 if input.is_empty() {
175 return (0, index);
176 }
177
178 let safe_index = index.min(input.len() - 1);
179 let column_offset = index - safe_index;
180 let index = safe_index;
181
182 let nl = input[0..index]
183 .iter()
184 .rev()
185 .enumerate()
186 .find(|(_, b)| **b == b'\n')
187 .map(|(nl, _)| index - nl - 1);
188 let line_start = match nl {
189 Some(nl) => nl + 1,
190 None => 0,
191 };
192 let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
193
194 let column = core::str::from_utf8(&input[line_start..=index])
195 .map(|s| s.chars().count() - 1)
196 .unwrap_or_else(|_| index - line_start);
197 let column = column + column_offset;
198
199 (line, column)
200}
201
202#[cfg(feature = "parse")]
203pub(crate) struct TomlSink<'i, S> {
204 source: toml_parser::Source<'i>,
205 input: Option<alloc::sync::Arc<str>>,
206 sink: S,
207}
208
209#[cfg(feature = "parse")]
210impl<'i, S: Default> TomlSink<'i, S> {
211 pub(crate) fn new(source: toml_parser::Source<'i>) -> Self {
212 Self {
213 source,
214 input: None,
215 sink: Default::default(),
216 }
217 }
218
219 pub(crate) fn into_inner(self) -> S {
220 self.sink
221 }
222}
223
224#[cfg(feature = "parse")]
225impl<'i> toml_parser::ErrorSink for TomlSink<'i, Option<Error>> {
226 fn report_error(&mut self, error: toml_parser::ParseError) {
227 if self.sink.is_none() {
228 let input = self
229 .input
230 .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
231 let error = Error::new(input.clone(), error);
232 self.sink = Some(error);
233 }
234 }
235}
236
237#[cfg(feature = "parse")]
238impl<'i> toml_parser::ErrorSink for TomlSink<'i, Vec<Error>> {
239 fn report_error(&mut self, error: toml_parser::ParseError) {
240 let input = self
241 .input
242 .get_or_insert_with(|| alloc::sync::Arc::from(self.source.input()));
243 let error = Error::new(input.clone(), error);
244 self.sink.push(error);
245 }
246}
247
248#[cfg(test)]
249mod test_translate_position {
250 use super::*;
251
252 #[test]
253 fn empty() {
254 let input = b"";
255 let index = 0;
256 let position = translate_position(&input[..], index);
257 assert_eq!(position, (0, 0));
258 }
259
260 #[test]
261 fn start() {
262 let input = b"Hello";
263 let index = 0;
264 let position = translate_position(&input[..], index);
265 assert_eq!(position, (0, 0));
266 }
267
268 #[test]
269 fn end() {
270 let input = b"Hello";
271 let index = input.len() - 1;
272 let position = translate_position(&input[..], index);
273 assert_eq!(position, (0, input.len() - 1));
274 }
275
276 #[test]
277 fn after() {
278 let input = b"Hello";
279 let index = input.len();
280 let position = translate_position(&input[..], index);
281 assert_eq!(position, (0, input.len()));
282 }
283
284 #[test]
285 fn first_line() {
286 let input = b"Hello\nWorld\n";
287 let index = 2;
288 let position = translate_position(&input[..], index);
289 assert_eq!(position, (0, 2));
290 }
291
292 #[test]
293 fn end_of_line() {
294 let input = b"Hello\nWorld\n";
295 let index = 5;
296 let position = translate_position(&input[..], index);
297 assert_eq!(position, (0, 5));
298 }
299
300 #[test]
301 fn start_of_second_line() {
302 let input = b"Hello\nWorld\n";
303 let index = 6;
304 let position = translate_position(&input[..], index);
305 assert_eq!(position, (1, 0));
306 }
307
308 #[test]
309 fn second_line() {
310 let input = b"Hello\nWorld\n";
311 let index = 8;
312 let position = translate_position(&input[..], index);
313 assert_eq!(position, (1, 2));
314 }
315}