1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug, Error)]
11pub enum Error {
12 #[error("Tokenization error at line {line}, column {column}: {message}")]
14 Tokenize {
15 message: String,
16 line: usize,
17 column: usize,
18 start: usize,
19 end: usize,
20 },
21
22 #[error("Parse error at line {line}, column {column}: {message}")]
24 Parse {
25 message: String,
26 line: usize,
27 column: usize,
28 start: usize,
29 end: usize,
30 },
31
32 #[error("Generation error: {0}")]
34 Generate(String),
35
36 #[error("Unsupported: {feature} is not supported in {dialect}")]
38 Unsupported { feature: String, dialect: String },
39
40 #[error("Syntax error at line {line}, column {column}: {message}")]
42 Syntax {
43 message: String,
44 line: usize,
45 column: usize,
46 start: usize,
47 end: usize,
48 },
49
50 #[error("Internal error: {0}")]
52 Internal(String),
53}
54
55impl Error {
56 pub fn tokenize(
58 message: impl Into<String>,
59 line: usize,
60 column: usize,
61 start: usize,
62 end: usize,
63 ) -> Self {
64 Error::Tokenize {
65 message: message.into(),
66 line,
67 column,
68 start,
69 end,
70 }
71 }
72
73 pub fn parse(
75 message: impl Into<String>,
76 line: usize,
77 column: usize,
78 start: usize,
79 end: usize,
80 ) -> Self {
81 Error::Parse {
82 message: message.into(),
83 line,
84 column,
85 start,
86 end,
87 }
88 }
89
90 pub fn line(&self) -> Option<usize> {
92 match self {
93 Error::Tokenize { line, .. }
94 | Error::Parse { line, .. }
95 | Error::Syntax { line, .. } => Some(*line),
96 _ => None,
97 }
98 }
99
100 pub fn column(&self) -> Option<usize> {
102 match self {
103 Error::Tokenize { column, .. }
104 | Error::Parse { column, .. }
105 | Error::Syntax { column, .. } => Some(*column),
106 _ => None,
107 }
108 }
109
110 pub fn start(&self) -> Option<usize> {
112 match self {
113 Error::Tokenize { start, .. }
114 | Error::Parse { start, .. }
115 | Error::Syntax { start, .. } => Some(*start),
116 _ => None,
117 }
118 }
119
120 pub fn end(&self) -> Option<usize> {
122 match self {
123 Error::Tokenize { end, .. } | Error::Parse { end, .. } | Error::Syntax { end, .. } => {
124 Some(*end)
125 }
126 _ => None,
127 }
128 }
129
130 pub fn generate(message: impl Into<String>) -> Self {
132 Error::Generate(message.into())
133 }
134
135 pub fn unsupported(feature: impl Into<String>, dialect: impl Into<String>) -> Self {
137 Error::Unsupported {
138 feature: feature.into(),
139 dialect: dialect.into(),
140 }
141 }
142
143 pub fn syntax(
145 message: impl Into<String>,
146 line: usize,
147 column: usize,
148 start: usize,
149 end: usize,
150 ) -> Self {
151 Error::Syntax {
152 message: message.into(),
153 line,
154 column,
155 start,
156 end,
157 }
158 }
159
160 pub fn internal(message: impl Into<String>) -> Self {
162 Error::Internal(message.into())
163 }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "lowercase")]
169pub enum ValidationSeverity {
170 Error,
172 Warning,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct ValidationError {
179 pub message: String,
181 pub line: Option<usize>,
183 pub column: Option<usize>,
185 pub severity: ValidationSeverity,
187 pub code: String,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub start: Option<usize>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub end: Option<usize>,
195}
196
197impl ValidationError {
198 pub fn error(message: impl Into<String>, code: impl Into<String>) -> Self {
200 Self {
201 message: message.into(),
202 line: None,
203 column: None,
204 severity: ValidationSeverity::Error,
205 code: code.into(),
206 start: None,
207 end: None,
208 }
209 }
210
211 pub fn warning(message: impl Into<String>, code: impl Into<String>) -> Self {
213 Self {
214 message: message.into(),
215 line: None,
216 column: None,
217 severity: ValidationSeverity::Warning,
218 code: code.into(),
219 start: None,
220 end: None,
221 }
222 }
223
224 pub fn with_line(mut self, line: usize) -> Self {
226 self.line = Some(line);
227 self
228 }
229
230 pub fn with_column(mut self, column: usize) -> Self {
232 self.column = Some(column);
233 self
234 }
235
236 pub fn with_location(mut self, line: usize, column: usize) -> Self {
238 self.line = Some(line);
239 self.column = Some(column);
240 self
241 }
242
243 pub fn with_span(mut self, start: Option<usize>, end: Option<usize>) -> Self {
245 self.start = start;
246 self.end = end;
247 self
248 }
249}
250
251#[derive(Debug, Serialize, Deserialize)]
253pub struct ValidationResult {
254 pub valid: bool,
256 pub errors: Vec<ValidationError>,
258}
259
260impl ValidationResult {
261 pub fn success() -> Self {
263 Self {
264 valid: true,
265 errors: Vec::new(),
266 }
267 }
268
269 pub fn with_errors(errors: Vec<ValidationError>) -> Self {
271 let has_errors = errors
272 .iter()
273 .any(|e| e.severity == ValidationSeverity::Error);
274 Self {
275 valid: !has_errors,
276 errors,
277 }
278 }
279
280 pub fn add_error(&mut self, error: ValidationError) {
282 if error.severity == ValidationSeverity::Error {
283 self.valid = false;
284 }
285 self.errors.push(error);
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_parse_error_has_position() {
295 let err = Error::parse("test message", 5, 10, 20, 25);
296 assert_eq!(err.line(), Some(5));
297 assert_eq!(err.column(), Some(10));
298 assert_eq!(err.start(), Some(20));
299 assert_eq!(err.end(), Some(25));
300 assert!(err.to_string().contains("line 5"));
301 assert!(err.to_string().contains("column 10"));
302 assert!(err.to_string().contains("test message"));
303 }
304
305 #[test]
306 fn test_tokenize_error_has_position() {
307 let err = Error::tokenize("bad token", 3, 7, 15, 20);
308 assert_eq!(err.line(), Some(3));
309 assert_eq!(err.column(), Some(7));
310 assert_eq!(err.start(), Some(15));
311 assert_eq!(err.end(), Some(20));
312 }
313
314 #[test]
315 fn test_generate_error_has_no_position() {
316 let err = Error::generate("gen error");
317 assert_eq!(err.line(), None);
318 assert_eq!(err.column(), None);
319 assert_eq!(err.start(), None);
320 assert_eq!(err.end(), None);
321 }
322
323 #[test]
324 fn test_parse_error_position_from_parser() {
325 use crate::dialects::{Dialect, DialectType};
327 let d = Dialect::get(DialectType::Generic);
328 let result = d.parse("SELECT 1 + 2)");
329 assert!(result.is_err());
330 let err = result.unwrap_err();
331 assert!(
332 err.line().is_some(),
333 "Parse error should have line: {:?}",
334 err
335 );
336 assert!(
337 err.column().is_some(),
338 "Parse error should have column: {:?}",
339 err
340 );
341 assert_eq!(err.line(), Some(1));
342 }
343
344 #[test]
345 fn test_parse_error_has_span_offsets() {
346 use crate::dialects::{Dialect, DialectType};
347 let d = Dialect::get(DialectType::Generic);
348 let result = d.parse("SELECT 1 + 2)");
349 assert!(result.is_err());
350 let err = result.unwrap_err();
351 assert!(
352 err.start().is_some(),
353 "Parse error should have start offset: {:?}",
354 err
355 );
356 assert!(
357 err.end().is_some(),
358 "Parse error should have end offset: {:?}",
359 err
360 );
361 assert_eq!(err.start(), Some(12));
363 assert_eq!(err.end(), Some(13));
364 }
365
366 #[test]
367 fn test_validation_error_with_span() {
368 let err = ValidationError::error("test", "E001")
369 .with_location(1, 5)
370 .with_span(Some(4), Some(10));
371 assert_eq!(err.start, Some(4));
372 assert_eq!(err.end, Some(10));
373 assert_eq!(err.line, Some(1));
374 assert_eq!(err.column, Some(5));
375 }
376}