toml_edit/
error.rs
1use std::error::Error as StdError;
2use std::fmt::{Display, Formatter, Result};
3
4#[derive(Debug, Clone, Eq, PartialEq, Hash)]
6pub struct TomlError {
7 message: String,
8 raw: Option<String>,
9 keys: Vec<String>,
10 span: Option<std::ops::Range<usize>>,
11}
12
13impl TomlError {
14 #[cfg(feature = "parse")]
15 pub(crate) fn new(
16 error: winnow::error::ParseError<
17 crate::parser::prelude::Input<'_>,
18 winnow::error::ContextError,
19 >,
20 mut raw: crate::parser::prelude::Input<'_>,
21 ) -> Self {
22 use winnow::stream::Stream;
23
24 let message = error.inner().to_string();
25 let raw = raw.finish();
26 let raw = String::from_utf8(raw.to_owned()).expect("original document was utf8");
27
28 let span = error.char_span();
29
30 Self {
31 message,
32 raw: Some(raw),
33 keys: Vec::new(),
34 span: Some(span),
35 }
36 }
37
38 #[cfg(any(feature = "serde", feature = "parse"))]
39 pub(crate) fn custom(message: String, span: Option<std::ops::Range<usize>>) -> Self {
40 Self {
41 message,
42 raw: None,
43 keys: Vec::new(),
44 span,
45 }
46 }
47
48 #[cfg(feature = "serde")]
49 pub(crate) fn add_key(&mut self, key: String) {
50 self.keys.insert(0, key);
51 }
52
53 pub fn message(&self) -> &str {
55 &self.message
56 }
57
58 pub fn span(&self) -> Option<std::ops::Range<usize>> {
60 self.span.clone()
61 }
62
63 #[cfg(feature = "serde")]
64 pub(crate) fn set_span(&mut self, span: Option<std::ops::Range<usize>>) {
65 self.span = span;
66 }
67
68 #[cfg(feature = "serde")]
69 pub(crate) fn set_raw(&mut self, raw: Option<String>) {
70 self.raw = raw;
71 }
72}
73
74impl Display for TomlError {
87 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
88 let mut context = false;
89 if let (Some(raw), Some(span)) = (&self.raw, self.span()) {
90 context = true;
91
92 let (line, column) = translate_position(raw.as_bytes(), span.start);
93 let line_num = line + 1;
94 let col_num = column + 1;
95 let gutter = line_num.to_string().len();
96 let content = raw.split('\n').nth(line).expect("valid line number");
97 let highlight_len = span.end - span.start;
98 let highlight_len = highlight_len.min(content.len().saturating_sub(column));
100
101 writeln!(f, "TOML parse error at line {line_num}, column {col_num}")?;
102 for _ in 0..=gutter {
104 write!(f, " ")?;
105 }
106 writeln!(f, "|")?;
107
108 write!(f, "{line_num} | ")?;
110 writeln!(f, "{content}")?;
111
112 for _ in 0..=gutter {
114 write!(f, " ")?;
115 }
116 write!(f, "|")?;
117 for _ in 0..=column {
118 write!(f, " ")?;
119 }
120 write!(f, "^")?;
123 for _ in 1..highlight_len {
124 write!(f, "^")?;
125 }
126 writeln!(f)?;
127 }
128 writeln!(f, "{}", self.message)?;
129 if !context && !self.keys.is_empty() {
130 writeln!(f, "in `{}`", self.keys.join("."))?;
131 }
132
133 Ok(())
134 }
135}
136
137impl StdError for TomlError {
138 fn description(&self) -> &'static str {
139 "TOML parse error"
140 }
141}
142
143fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
144 if input.is_empty() {
145 return (0, index);
146 }
147
148 let safe_index = index.min(input.len() - 1);
149 let column_offset = index - safe_index;
150 let index = safe_index;
151
152 let nl = input[0..index]
153 .iter()
154 .rev()
155 .enumerate()
156 .find(|(_, b)| **b == b'\n')
157 .map(|(nl, _)| index - nl - 1);
158 let line_start = match nl {
159 Some(nl) => nl + 1,
160 None => 0,
161 };
162 let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
163
164 let column = std::str::from_utf8(&input[line_start..=index])
165 .map(|s| s.chars().count() - 1)
166 .unwrap_or_else(|_| index - line_start);
167 let column = column + column_offset;
168
169 (line, column)
170}
171
172#[cfg(test)]
173mod test_translate_position {
174 use super::*;
175
176 #[test]
177 fn empty() {
178 let input = b"";
179 let index = 0;
180 let position = translate_position(&input[..], index);
181 assert_eq!(position, (0, 0));
182 }
183
184 #[test]
185 fn start() {
186 let input = b"Hello";
187 let index = 0;
188 let position = translate_position(&input[..], index);
189 assert_eq!(position, (0, 0));
190 }
191
192 #[test]
193 fn end() {
194 let input = b"Hello";
195 let index = input.len() - 1;
196 let position = translate_position(&input[..], index);
197 assert_eq!(position, (0, input.len() - 1));
198 }
199
200 #[test]
201 fn after() {
202 let input = b"Hello";
203 let index = input.len();
204 let position = translate_position(&input[..], index);
205 assert_eq!(position, (0, input.len()));
206 }
207
208 #[test]
209 fn first_line() {
210 let input = b"Hello\nWorld\n";
211 let index = 2;
212 let position = translate_position(&input[..], index);
213 assert_eq!(position, (0, 2));
214 }
215
216 #[test]
217 fn end_of_line() {
218 let input = b"Hello\nWorld\n";
219 let index = 5;
220 let position = translate_position(&input[..], index);
221 assert_eq!(position, (0, 5));
222 }
223
224 #[test]
225 fn start_of_second_line() {
226 let input = b"Hello\nWorld\n";
227 let index = 6;
228 let position = translate_position(&input[..], index);
229 assert_eq!(position, (1, 0));
230 }
231
232 #[test]
233 fn second_line() {
234 let input = b"Hello\nWorld\n";
235 let index = 8;
236 let position = translate_position(&input[..], index);
237 assert_eq!(position, (1, 2));
238 }
239}