1use logos::Span;
7use std::{error, fmt, result};
8
9pub type Result<T> = result::Result<T, Error>;
11
12#[derive(PartialEq, Eq, Clone)]
21pub struct Error {
22 pub(crate) message: String,
23 pub(crate) location: Option<Location>,
24 pub(crate) context: Option<String>,
25 pub(crate) error_type: ErrorType,
26}
27
28#[derive(PartialEq, Eq, Clone)]
29pub enum ErrorType {
30 GraphQL,
31 Syntax,
32}
33
34impl Error {
35 pub fn new<S: Into<String>>(message: S, error_type: Option<ErrorType>) -> Self {
37 Self {
38 message: message.into(),
39 location: None,
40 context: None,
41 error_type: error_type.unwrap_or(ErrorType::GraphQL),
42 }
43 }
44
45 pub fn new_with_context<S: Into<String>>(
47 message: S,
48 location: Option<Location>,
49 context: S,
50 error_type: Option<ErrorType>,
51 ) -> Self {
52 Self {
53 message: message.into(),
54 location,
55 context: Some(context.into()),
56 error_type: error_type.unwrap_or(ErrorType::GraphQL),
57 }
58 }
59
60 pub fn message(&self) -> &str {
62 self.message.as_ref()
63 }
64
65 pub fn location(&self) -> &Option<Location> {
67 &self.location
68 }
69
70 pub fn print(&self, include_ctx: bool) -> String {
73 let formatted = match self.error_type {
74 ErrorType::GraphQL => {
75 format!("GraphQL Error: {}", self.message)
76 }
77 ErrorType::Syntax => {
78 format!("Syntax Error: {}", self.message)
79 }
80 };
81
82 match self.context {
83 Some(ref context) if include_ctx => format!("{}\n{}", formatted, context),
84 _ => formatted,
85 }
86 }
87}
88
89pub(crate) fn print_span(source: &str, span: Span) -> String {
90 let mut out = String::new();
91 let start_line = source[..span.start].lines().count();
92
93 let start = source[..span.start]
94 .rfind('\n')
95 .and_then(|start| source[..start].rfind('\n'))
96 .map_or(0, |idx| idx + 1);
97
98 let end = source[span.end..]
99 .find('\n')
100 .map_or(source.len(), |idx| idx + span.end);
101
102 let snippet = &source[start..end];
103 let line_num_pad = (start_line + snippet.lines().count() - 1).to_string().len();
104 for (index, line) in snippet.lines().enumerate() {
105 if index > 0 {
106 out.push('\n');
107 }
108 let line_num = (start_line + index).to_string();
109 out.push_str(&" ".repeat(line_num_pad - line_num.len() + 1));
110 out.push_str(&line_num);
111 out.push_str(" | ");
112 out.push_str(line);
113 }
114 if source[span.start..span.end].find('\n').is_none() {
115 let start = source[..span.start].rfind('\n').map_or(0, |idx| idx + 1);
116 out.push('\n');
117 out.push_str(&" ".repeat(line_num_pad + 1));
118 out.push_str(" | ");
119 out.push_str(&" ".repeat(span.start - start));
120 out.push_str(&"^".repeat(span.end - span.start));
121 };
122
123 out
124}
125
126#[derive(Debug, PartialEq, Eq, Clone)]
127pub struct Location {
128 pub line: usize,
129 pub column: usize,
130}
131
132pub(crate) fn get_location(source: &str, span: Span) -> Location {
133 let line = source[..span.start].lines().count();
134 let col = source[..span.start]
135 .lines()
136 .last()
137 .map_or(span.start, |x| x.len());
138
139 Location { line, column: col }
140}
141
142impl fmt::Display for Error {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "{}", self.print(true))
145 }
146}
147
148impl fmt::Debug for Error {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "\n{}\n", self)
151 }
152}
153
154impl error::Error for Error {}