1use serde::{Deserialize, Serialize};
4use std::fmt;
5use thiserror::Error;
6
7pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Debug, Error)]
12#[non_exhaustive]
13pub enum Error {
14 #[error("Tokenization error at line {line}, column {column}: {message}")]
16 Tokenize {
17 message: String,
18 line: usize,
19 column: usize,
20 start: usize,
21 end: usize,
22 },
23
24 #[error("Parse error at line {line}, column {column}: {message}")]
26 Parse {
27 message: String,
28 line: usize,
29 column: usize,
30 start: usize,
31 end: usize,
32 },
33
34 #[error("Generation error: {0}")]
36 Generate(String),
37
38 #[error("Unsupported: {feature} is not supported in {dialect}")]
40 Unsupported { feature: String, dialect: String },
41
42 #[error("Syntax error at line {line}, column {column}: {message}")]
44 Syntax {
45 message: String,
46 line: usize,
47 column: usize,
48 start: usize,
49 end: usize,
50 },
51
52 #[error("Invalid input: {0}")]
54 InvalidInput(String),
55
56 #[error("Cannot resolve {target}: {reason}")]
58 ColumnResolution {
59 target: ColumnResolutionTarget,
60 reason: ColumnResolutionReason,
61 },
62
63 #[error("Internal error: {0}")]
65 Internal(String),
66}
67
68impl Error {
69 pub fn tokenize(
71 message: impl Into<String>,
72 line: usize,
73 column: usize,
74 start: usize,
75 end: usize,
76 ) -> Self {
77 Error::Tokenize {
78 message: message.into(),
79 line,
80 column,
81 start,
82 end,
83 }
84 }
85
86 pub fn parse(
88 message: impl Into<String>,
89 line: usize,
90 column: usize,
91 start: usize,
92 end: usize,
93 ) -> Self {
94 Error::Parse {
95 message: message.into(),
96 line,
97 column,
98 start,
99 end,
100 }
101 }
102
103 pub fn line(&self) -> Option<usize> {
105 match self {
106 Error::Tokenize { line, .. }
107 | Error::Parse { line, .. }
108 | Error::Syntax { line, .. } => Some(*line),
109 _ => None,
110 }
111 }
112
113 pub fn column(&self) -> Option<usize> {
115 match self {
116 Error::Tokenize { column, .. }
117 | Error::Parse { column, .. }
118 | Error::Syntax { column, .. } => Some(*column),
119 _ => None,
120 }
121 }
122
123 pub fn start(&self) -> Option<usize> {
125 match self {
126 Error::Tokenize { start, .. }
127 | Error::Parse { start, .. }
128 | Error::Syntax { start, .. } => Some(*start),
129 _ => None,
130 }
131 }
132
133 pub fn end(&self) -> Option<usize> {
135 match self {
136 Error::Tokenize { end, .. } | Error::Parse { end, .. } | Error::Syntax { end, .. } => {
137 Some(*end)
138 }
139 _ => None,
140 }
141 }
142
143 pub fn generate(message: impl Into<String>) -> Self {
145 Error::Generate(message.into())
146 }
147
148 pub fn unsupported(feature: impl Into<String>, dialect: impl Into<String>) -> Self {
150 Error::Unsupported {
151 feature: feature.into(),
152 dialect: dialect.into(),
153 }
154 }
155
156 pub fn syntax(
158 message: impl Into<String>,
159 line: usize,
160 column: usize,
161 start: usize,
162 end: usize,
163 ) -> Self {
164 Error::Syntax {
165 message: message.into(),
166 line,
167 column,
168 start,
169 end,
170 }
171 }
172
173 pub fn invalid_input(message: impl Into<String>) -> Self {
175 Error::InvalidInput(message.into())
176 }
177
178 pub fn column_resolution(
180 target: ColumnResolutionTarget,
181 reason: ColumnResolutionReason,
182 ) -> Self {
183 Error::ColumnResolution { target, reason }
184 }
185
186 pub fn internal(message: impl Into<String>) -> Self {
188 Error::Internal(message.into())
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(tag = "kind", rename_all = "snake_case")]
195pub enum ColumnResolutionTarget {
196 Name { name: String },
198 Ordinal { ordinal: usize },
200}
201
202impl fmt::Display for ColumnResolutionTarget {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 Self::Name { name } => write!(f, "column '{name}'"),
206 Self::Ordinal { ordinal } => write!(f, "output ordinal {ordinal}"),
207 }
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214pub enum ColumnResolutionReason {
215 NotFound,
217 Indeterminate,
219 Ambiguous,
221}
222
223impl fmt::Display for ColumnResolutionReason {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 match self {
226 Self::NotFound => f.write_str("not found"),
227 Self::Indeterminate => {
228 f.write_str("indeterminate because an output wildcard could not be expanded")
229 }
230 Self::Ambiguous => f.write_str("ambiguous"),
231 }
232 }
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
237#[serde(rename_all = "lowercase")]
238pub enum ValidationSeverity {
239 Error,
241 Warning,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct ValidationError {
248 pub message: String,
250 pub line: Option<usize>,
252 pub column: Option<usize>,
254 pub severity: ValidationSeverity,
256 pub code: String,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub start: Option<usize>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub end: Option<usize>,
264}
265
266impl ValidationError {
267 pub fn error(message: impl Into<String>, code: impl Into<String>) -> Self {
269 Self {
270 message: message.into(),
271 line: None,
272 column: None,
273 severity: ValidationSeverity::Error,
274 code: code.into(),
275 start: None,
276 end: None,
277 }
278 }
279
280 pub fn warning(message: impl Into<String>, code: impl Into<String>) -> Self {
282 Self {
283 message: message.into(),
284 line: None,
285 column: None,
286 severity: ValidationSeverity::Warning,
287 code: code.into(),
288 start: None,
289 end: None,
290 }
291 }
292
293 pub fn with_line(mut self, line: usize) -> Self {
295 self.line = Some(line);
296 self
297 }
298
299 pub fn with_column(mut self, column: usize) -> Self {
301 self.column = Some(column);
302 self
303 }
304
305 pub fn with_location(mut self, line: usize, column: usize) -> Self {
307 self.line = Some(line);
308 self.column = Some(column);
309 self
310 }
311
312 pub fn with_span(mut self, start: Option<usize>, end: Option<usize>) -> Self {
314 self.start = start;
315 self.end = end;
316 self
317 }
318}
319
320#[derive(Debug, Serialize, Deserialize)]
322pub struct ValidationResult {
323 pub valid: bool,
325 pub errors: Vec<ValidationError>,
327}
328
329impl ValidationResult {
330 pub fn success() -> Self {
332 Self {
333 valid: true,
334 errors: Vec::new(),
335 }
336 }
337
338 pub fn with_errors(errors: Vec<ValidationError>) -> Self {
340 let has_errors = errors
341 .iter()
342 .any(|e| e.severity == ValidationSeverity::Error);
343 Self {
344 valid: !has_errors,
345 errors,
346 }
347 }
348
349 pub fn add_error(&mut self, error: ValidationError) {
351 if error.severity == ValidationSeverity::Error {
352 self.valid = false;
353 }
354 self.errors.push(error);
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_parse_error_has_position() {
364 let err = Error::parse("test message", 5, 10, 20, 25);
365 assert_eq!(err.line(), Some(5));
366 assert_eq!(err.column(), Some(10));
367 assert_eq!(err.start(), Some(20));
368 assert_eq!(err.end(), Some(25));
369 assert!(err.to_string().contains("line 5"));
370 assert!(err.to_string().contains("column 10"));
371 assert!(err.to_string().contains("test message"));
372 }
373
374 #[test]
375 fn test_tokenize_error_has_position() {
376 let err = Error::tokenize("bad token", 3, 7, 15, 20);
377 assert_eq!(err.line(), Some(3));
378 assert_eq!(err.column(), Some(7));
379 assert_eq!(err.start(), Some(15));
380 assert_eq!(err.end(), Some(20));
381 }
382
383 #[test]
384 fn test_generate_error_has_no_position() {
385 let err = Error::generate("gen error");
386 assert_eq!(err.line(), None);
387 assert_eq!(err.column(), None);
388 assert_eq!(err.start(), None);
389 assert_eq!(err.end(), None);
390 }
391
392 #[test]
393 fn test_parse_error_position_from_parser() {
394 use crate::dialects::{Dialect, DialectType};
396 let d = Dialect::get(DialectType::Generic);
397 let result = d.parse("SELECT 1 + 2)");
398 assert!(result.is_err());
399 let err = result.unwrap_err();
400 assert!(
401 err.line().is_some(),
402 "Parse error should have line: {:?}",
403 err
404 );
405 assert!(
406 err.column().is_some(),
407 "Parse error should have column: {:?}",
408 err
409 );
410 assert_eq!(err.line(), Some(1));
411 }
412
413 #[test]
414 fn test_parse_error_has_span_offsets() {
415 use crate::dialects::{Dialect, DialectType};
416 let d = Dialect::get(DialectType::Generic);
417 let result = d.parse("SELECT 1 + 2)");
418 assert!(result.is_err());
419 let err = result.unwrap_err();
420 assert!(
421 err.start().is_some(),
422 "Parse error should have start offset: {:?}",
423 err
424 );
425 assert!(
426 err.end().is_some(),
427 "Parse error should have end offset: {:?}",
428 err
429 );
430 assert_eq!(err.start(), Some(12));
432 assert_eq!(err.end(), Some(13));
433 }
434
435 #[test]
436 fn test_validation_error_with_span() {
437 let err = ValidationError::error("test", "E001")
438 .with_location(1, 5)
439 .with_span(Some(4), Some(10));
440 assert_eq!(err.start, Some(4));
441 assert_eq!(err.end, Some(10));
442 assert_eq!(err.line, Some(1));
443 assert_eq!(err.column, Some(5));
444 }
445}