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 },
19
20 #[error("Parse error at line {line}, column {column}: {message}")]
22 Parse {
23 message: String,
24 line: usize,
25 column: usize,
26 },
27
28 #[error("Generation error: {0}")]
30 Generate(String),
31
32 #[error("Unsupported: {feature} is not supported in {dialect}")]
34 Unsupported { feature: String, dialect: String },
35
36 #[error("Syntax error at line {line}, column {column}: {message}")]
38 Syntax {
39 message: String,
40 line: usize,
41 column: usize,
42 },
43
44 #[error("Internal error: {0}")]
46 Internal(String),
47}
48
49impl Error {
50 pub fn tokenize(message: impl Into<String>, line: usize, column: usize) -> Self {
52 Error::Tokenize {
53 message: message.into(),
54 line,
55 column,
56 }
57 }
58
59 pub fn parse(message: impl Into<String>, line: usize, column: usize) -> Self {
61 Error::Parse {
62 message: message.into(),
63 line,
64 column,
65 }
66 }
67
68 pub fn line(&self) -> Option<usize> {
70 match self {
71 Error::Tokenize { line, .. }
72 | Error::Parse { line, .. }
73 | Error::Syntax { line, .. } => Some(*line),
74 _ => None,
75 }
76 }
77
78 pub fn column(&self) -> Option<usize> {
80 match self {
81 Error::Tokenize { column, .. }
82 | Error::Parse { column, .. }
83 | Error::Syntax { column, .. } => Some(*column),
84 _ => None,
85 }
86 }
87
88 pub fn generate(message: impl Into<String>) -> Self {
90 Error::Generate(message.into())
91 }
92
93 pub fn unsupported(feature: impl Into<String>, dialect: impl Into<String>) -> Self {
95 Error::Unsupported {
96 feature: feature.into(),
97 dialect: dialect.into(),
98 }
99 }
100
101 pub fn syntax(message: impl Into<String>, line: usize, column: usize) -> Self {
103 Error::Syntax {
104 message: message.into(),
105 line,
106 column,
107 }
108 }
109
110 pub fn internal(message: impl Into<String>) -> Self {
112 Error::Internal(message.into())
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118#[serde(rename_all = "lowercase")]
119pub enum ValidationSeverity {
120 Error,
122 Warning,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ValidationError {
129 pub message: String,
131 pub line: Option<usize>,
133 pub column: Option<usize>,
135 pub severity: ValidationSeverity,
137 pub code: String,
139}
140
141impl ValidationError {
142 pub fn error(message: impl Into<String>, code: impl Into<String>) -> Self {
144 Self {
145 message: message.into(),
146 line: None,
147 column: None,
148 severity: ValidationSeverity::Error,
149 code: code.into(),
150 }
151 }
152
153 pub fn warning(message: impl Into<String>, code: impl Into<String>) -> Self {
155 Self {
156 message: message.into(),
157 line: None,
158 column: None,
159 severity: ValidationSeverity::Warning,
160 code: code.into(),
161 }
162 }
163
164 pub fn with_line(mut self, line: usize) -> Self {
166 self.line = Some(line);
167 self
168 }
169
170 pub fn with_column(mut self, column: usize) -> Self {
172 self.column = Some(column);
173 self
174 }
175
176 pub fn with_location(mut self, line: usize, column: usize) -> Self {
178 self.line = Some(line);
179 self.column = Some(column);
180 self
181 }
182}
183
184#[derive(Debug, Serialize, Deserialize)]
186pub struct ValidationResult {
187 pub valid: bool,
189 pub errors: Vec<ValidationError>,
191}
192
193impl ValidationResult {
194 pub fn success() -> Self {
196 Self {
197 valid: true,
198 errors: Vec::new(),
199 }
200 }
201
202 pub fn with_errors(errors: Vec<ValidationError>) -> Self {
204 let has_errors = errors
205 .iter()
206 .any(|e| e.severity == ValidationSeverity::Error);
207 Self {
208 valid: !has_errors,
209 errors,
210 }
211 }
212
213 pub fn add_error(&mut self, error: ValidationError) {
215 if error.severity == ValidationSeverity::Error {
216 self.valid = false;
217 }
218 self.errors.push(error);
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_parse_error_has_position() {
228 let err = Error::parse("test message", 5, 10);
229 assert_eq!(err.line(), Some(5));
230 assert_eq!(err.column(), Some(10));
231 assert!(err.to_string().contains("line 5"));
232 assert!(err.to_string().contains("column 10"));
233 assert!(err.to_string().contains("test message"));
234 }
235
236 #[test]
237 fn test_tokenize_error_has_position() {
238 let err = Error::tokenize("bad token", 3, 7);
239 assert_eq!(err.line(), Some(3));
240 assert_eq!(err.column(), Some(7));
241 }
242
243 #[test]
244 fn test_generate_error_has_no_position() {
245 let err = Error::generate("gen error");
246 assert_eq!(err.line(), None);
247 assert_eq!(err.column(), None);
248 }
249
250 #[test]
251 fn test_parse_error_position_from_parser() {
252 use crate::dialects::{Dialect, DialectType};
254 let d = Dialect::get(DialectType::Generic);
255 let result = d.parse("SELECT 1 + 2)");
256 assert!(result.is_err());
257 let err = result.unwrap_err();
258 assert!(
259 err.line().is_some(),
260 "Parse error should have line: {:?}",
261 err
262 );
263 assert!(
264 err.column().is_some(),
265 "Parse error should have column: {:?}",
266 err
267 );
268 assert_eq!(err.line(), Some(1));
269 }
270}