libgraphql_parser/
graphql_parse_error.rs1use crate::GraphQLErrorNote;
2use crate::GraphQLErrorNoteKind;
3use crate::GraphQLErrorNotes;
4use crate::GraphQLParseErrorKind;
5use crate::GraphQLSourceSpan;
6
7#[derive(Debug, Clone, thiserror::Error)]
12#[error("{}", self.format_oneline())]
13pub struct GraphQLParseError {
14 message: String,
19
20 span: GraphQLSourceSpan,
27
28 kind: GraphQLParseErrorKind,
32
33 notes: GraphQLErrorNotes,
41}
42
43impl GraphQLParseError {
44 pub fn new(
46 message: impl Into<String>,
47 span: GraphQLSourceSpan,
48 kind: GraphQLParseErrorKind,
49 ) -> Self {
50 Self {
51 message: message.into(),
52 span,
53 kind,
54 notes: GraphQLErrorNotes::new(),
55 }
56 }
57
58 pub fn with_notes(
60 message: impl Into<String>,
61 span: GraphQLSourceSpan,
62 kind: GraphQLParseErrorKind,
63 notes: GraphQLErrorNotes,
64 ) -> Self {
65 Self {
66 message: message.into(),
67 span,
68 kind,
69 notes,
70 }
71 }
72
73 pub fn from_lexer_error(
79 message: impl Into<String>,
80 span: GraphQLSourceSpan,
81 lexer_notes: GraphQLErrorNotes,
82 ) -> Self {
83 Self {
84 message: message.into(),
85 span,
86 kind: GraphQLParseErrorKind::LexerError,
87 notes: lexer_notes,
88 }
89 }
90
91 pub fn message(&self) -> &str {
93 &self.message
94 }
95
96 pub fn span(&self) -> &GraphQLSourceSpan {
98 &self.span
99 }
100
101 pub fn kind(&self) -> &GraphQLParseErrorKind {
103 &self.kind
104 }
105
106 pub fn notes(&self) -> &GraphQLErrorNotes {
108 &self.notes
109 }
110
111 pub fn add_note(&mut self, message: impl Into<String>) {
113 self.notes.push(GraphQLErrorNote::general(message));
114 }
115
116 pub fn add_note_with_span(&mut self, message: impl Into<String>, span: GraphQLSourceSpan) {
118 self.notes
119 .push(GraphQLErrorNote::general_with_span(message, span));
120 }
121
122 pub fn add_help(&mut self, message: impl Into<String>) {
124 self.notes.push(GraphQLErrorNote::help(message));
125 }
126
127 pub fn add_help_with_span(&mut self, message: impl Into<String>, span: GraphQLSourceSpan) {
129 self.notes
130 .push(GraphQLErrorNote::help_with_span(message, span));
131 }
132
133 pub fn add_spec(&mut self, url: impl Into<String>) {
135 self.notes.push(GraphQLErrorNote::spec(url));
136 }
137
138 pub fn format_detailed(&self, source: Option<&str>) -> String {
156 let mut output = String::new();
157
158 output.push_str("error: ");
160 output.push_str(&self.message);
161 output.push('\n');
162
163 let file_name = self
165 .span
166 .file_path
167 .as_ref()
168 .map(|p| p.display().to_string())
169 .unwrap_or_else(|| "<input>".to_string());
170 let line = self.span.start_inclusive.line() + 1;
171 let column = self.span.start_inclusive.col_utf8() + 1;
172 output.push_str(&format!(" --> {file_name}:{line}:{column}\n"));
173
174 if let Some(src) = source
176 && let Some(snippet) = self.format_source_snippet(src)
177 {
178 output.push_str(&snippet);
179 }
180
181 for note in &self.notes {
183 let prefix = match note.kind {
184 GraphQLErrorNoteKind::General => "note",
185 GraphQLErrorNoteKind::Help => "help",
186 GraphQLErrorNoteKind::Spec => "spec",
187 };
188 output.push_str(&format!(" = {prefix}: {}\n", note.message));
189
190 if let (Some(note_span), Some(src)) = (¬e.span, source)
192 && let Some(snippet) = self.format_note_snippet(src, note_span)
193 {
194 output.push_str(&snippet);
195 }
196 }
197
198 output
199 }
200
201 pub fn format_oneline(&self) -> String {
208 let file_name = self
209 .span
210 .file_path
211 .as_ref()
212 .map(|p| p.display().to_string())
213 .unwrap_or_else(|| "<input>".to_string());
214 let line = self.span.start_inclusive.line() + 1;
215 let column = self.span.start_inclusive.col_utf8() + 1;
216
217 format!("{file_name}:{line}:{column}: error: {}", self.message)
218 }
219
220 fn format_source_snippet(&self, source: &str) -> Option<String> {
222 let lines: Vec<&str> = source.lines().collect();
223 let line_num = self.span.start_inclusive.line();
224
225 if line_num >= lines.len() {
227 return None;
228 }
229
230 let line_content = lines[line_num];
231 let display_line_num = line_num + 1; let line_num_width = display_line_num.to_string().len().max(2);
233
234 let mut output = String::new();
235
236 output.push_str(&format!("{:>width$} |\n", "", width = line_num_width));
238
239 output.push_str(&format!(
241 "{display_line_num:>line_num_width$} | {line_content}\n"
242 ));
243
244 let col_start = self.span.start_inclusive.col_utf8();
246 let col_end = self.span.end_exclusive.col_utf8();
247 let underline_len = if col_end > col_start {
248 col_end - col_start
249 } else {
250 1
251 };
252
253 output.push_str(&format!(
254 "{:>width$} | {:>padding$}{}\n",
255 "",
256 "",
257 "^".repeat(underline_len),
258 width = line_num_width,
259 padding = col_start
260 ));
261
262 Some(output)
263 }
264
265 fn format_note_snippet(&self, source: &str, span: &GraphQLSourceSpan) -> Option<String> {
267 let lines: Vec<&str> = source.lines().collect();
268 let line_num = span.start_inclusive.line();
269
270 if line_num >= lines.len() {
272 return None;
273 }
274
275 let line_content = lines[line_num];
276 let display_line_num = line_num + 1; let line_num_width = display_line_num.to_string().len().max(2);
278
279 let mut output = String::new();
280
281 output.push_str(&format!(
283 " {display_line_num:>line_num_width$} | {line_content}\n"
284 ));
285
286 let col_start = span.start_inclusive.col_utf8();
288 output.push_str(&format!(
289 " {:>width$} | {:>padding$}-\n",
290 "",
291 "",
292 width = line_num_width,
293 padding = col_start
294 ));
295
296 Some(output)
297 }
298}