1use std::fmt::Debug;
2
3use serde::Serialize;
4
5use crate::span::Span;
6
7#[derive(Debug, Clone)]
9pub struct Error {
10 pub kind: MessageKind,
12 pub span: Option<Span>,
13 pub reason: Reason,
14 pub hints: Vec<String>,
15 pub code: Option<&'static str>,
17 }
19
20#[derive(Clone, Debug, Default)]
21pub enum ErrorSource {
22 Lexer(String),
23 #[default]
24 Unknown,
25 NameResolver,
26 TypeResolver,
27 SQL,
28 Internal {
29 message: String,
30 },
31}
32
33#[derive(Debug, Clone)]
35pub struct Errors(pub Vec<Error>);
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
39pub enum MessageKind {
40 Error,
41 Warning,
42 Lint,
43}
44
45#[derive(Debug, Clone)]
46pub enum Reason {
47 Simple(String),
48 Expected {
49 who: Option<String>,
52 expected: String,
54 found: String,
56 },
57 Unexpected {
58 found: String,
59 },
60 NotFound {
61 name: String,
62 namespace: String,
63 },
64 Bug {
65 issue: Option<i32>,
66 details: Option<String>,
67 },
68 Internal {
69 message: String,
70 },
71}
72
73impl Error {
74 pub fn new(reason: Reason) -> Self {
75 Error {
76 kind: MessageKind::Error,
77 span: None,
78 reason,
79 hints: Vec::new(),
80 code: None,
81 }
83 }
84
85 pub fn new_simple<S: ToString>(reason: S) -> Self {
86 Error::new(Reason::Simple(reason.to_string()))
87 }
88
89 pub fn new_bug(issue_no: i32) -> Self {
90 Error::new(Reason::Bug {
91 issue: Some(issue_no),
92 details: None,
93 })
94 }
95
96 pub fn new_assert<S: ToString>(details: S) -> Self {
98 Error::new(Reason::Bug {
99 issue: None,
100 details: Some(details.to_string()),
101 })
102 }
103}
104
105impl std::fmt::Display for Reason {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 match self {
108 Reason::Simple(text) => f.write_str(text),
109 Reason::Expected {
110 who,
111 expected,
112 found,
113 } => {
114 if let Some(who) = who {
115 write!(f, "{who} ")?;
116 }
117 write!(f, "expected {expected}, but found {found}")
118 }
119 Reason::Unexpected { found } => write!(f, "unexpected {found}"),
120 Reason::NotFound { name, namespace } => write!(f, "{namespace} `{name}` not found"),
121 Reason::Bug { issue, details } => {
122 write!(f, "internal compiler error")?;
123 if let Some(details) = details {
124 write!(f, "; {details}")?;
125 }
126 if let Some(issue_no) = issue {
127 write!(
128 f,
129 "; tracked at https://github.com/PRQL/prql/issues/{issue_no}"
130 )?;
131 }
132 Ok(())
133 }
134 Reason::Internal { message } => {
135 write!(f, "internal error: {message}")
136 }
137 }
138 }
139}
140
141impl From<Error> for Errors {
142 fn from(error: Error) -> Self {
143 Errors(vec![error])
144 }
145}
146
147impl std::error::Error for Error {}
149
150impl std::error::Error for Errors {}
152
153impl std::fmt::Display for Error {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 Debug::fmt(&self, f)
157 }
158}
159
160impl std::fmt::Display for Errors {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 Debug::fmt(&self, f)
164 }
165}
166
167pub trait WithErrorInfo: Sized {
168 fn push_hint<S: Into<String>>(self, hint: S) -> Self;
169
170 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self;
171
172 fn with_span(self, span: Option<Span>) -> Self;
173
174 fn with_span_fallback(self, span: Option<Span>) -> Self;
175
176 fn with_code(self, code: &'static str) -> Self;
177
178 fn with_source(self, source: ErrorSource) -> Self;
179}
180
181impl WithErrorInfo for Error {
182 fn push_hint<S: Into<String>>(mut self, hint: S) -> Self {
183 self.hints.push(hint.into());
184 self
185 }
186
187 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(mut self, hints: I) -> Self {
188 self.hints = hints.into_iter().map(|x| x.into()).collect();
189 self
190 }
191
192 fn with_span(mut self, span: Option<Span>) -> Self {
193 self.span = span;
194 self
195 }
196
197 fn with_code(mut self, code: &'static str) -> Self {
198 self.code = Some(code);
199 self
200 }
201
202 fn with_span_fallback(mut self, span: Option<Span>) -> Self {
203 self.span = self.span.or(span);
204 self
205 }
206
207 fn with_source(self, _source: ErrorSource) -> Self {
208 self
210 }
211}
212
213impl<T, E: WithErrorInfo> WithErrorInfo for Result<T, E> {
214 fn push_hint<S: Into<String>>(self, hint: S) -> Self {
215 self.map_err(|e| e.push_hint(hint))
216 }
217
218 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self {
219 self.map_err(|e| e.with_hints(hints))
220 }
221
222 fn with_span(self, span: Option<Span>) -> Self {
223 self.map_err(|e| e.with_span(span))
224 }
225
226 fn with_span_fallback(self, span: Option<Span>) -> Self {
227 self.map_err(|e| e.with_span_fallback(span))
228 }
229
230 fn with_code(self, code: &'static str) -> Self {
231 self.map_err(|e| e.with_code(code))
232 }
233
234 fn with_source(self, source: ErrorSource) -> Self {
235 self.map_err(|e| e.with_source(source))
236 }
237}