Skip to main content

polyglot_sql/
error.rs

1//! Error types for polyglot-sql
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6/// The result type for polyglot operations
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Errors that can occur during SQL parsing and generation
10#[derive(Debug, Error)]
11pub enum Error {
12    /// Error during tokenization
13    #[error("Tokenization error at line {line}, column {column}: {message}")]
14    Tokenize {
15        message: String,
16        line: usize,
17        column: usize,
18    },
19
20    /// Error during parsing
21    #[error("Parse error at line {line}, column {column}: {message}")]
22    Parse {
23        message: String,
24        line: usize,
25        column: usize,
26    },
27
28    /// Error during SQL generation
29    #[error("Generation error: {0}")]
30    Generate(String),
31
32    /// Unsupported feature for the target dialect
33    #[error("Unsupported: {feature} is not supported in {dialect}")]
34    Unsupported { feature: String, dialect: String },
35
36    /// Invalid SQL syntax
37    #[error("Syntax error at line {line}, column {column}: {message}")]
38    Syntax {
39        message: String,
40        line: usize,
41        column: usize,
42    },
43
44    /// Internal error (should not happen in normal usage)
45    #[error("Internal error: {0}")]
46    Internal(String),
47}
48
49impl Error {
50    /// Create a tokenization error
51    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    /// Create a parse error with position information
60    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    /// Get the line number if available
69    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    /// Get the column number if available
79    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    /// Create a generation error
89    pub fn generate(message: impl Into<String>) -> Self {
90        Error::Generate(message.into())
91    }
92
93    /// Create an unsupported feature error
94    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    /// Create a syntax error
102    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    /// Create an internal error
111    pub fn internal(message: impl Into<String>) -> Self {
112        Error::Internal(message.into())
113    }
114}
115
116/// Severity level for validation errors
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118#[serde(rename_all = "lowercase")]
119pub enum ValidationSeverity {
120    /// An error that prevents the query from being valid
121    Error,
122    /// A warning about potential issues
123    Warning,
124}
125
126/// A single validation error or warning
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ValidationError {
129    /// The error/warning message
130    pub message: String,
131    /// Line number where the error occurred (1-based)
132    pub line: Option<usize>,
133    /// Column number where the error occurred (1-based)
134    pub column: Option<usize>,
135    /// Severity of the validation issue
136    pub severity: ValidationSeverity,
137    /// Error code (e.g., "E001", "W001")
138    pub code: String,
139}
140
141impl ValidationError {
142    /// Create a new validation error
143    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    /// Create a new validation warning
154    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    /// Set the line number
165    pub fn with_line(mut self, line: usize) -> Self {
166        self.line = Some(line);
167        self
168    }
169
170    /// Set the column number
171    pub fn with_column(mut self, column: usize) -> Self {
172        self.column = Some(column);
173        self
174    }
175
176    /// Set both line and column
177    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/// Result of validating SQL
185#[derive(Debug, Serialize, Deserialize)]
186pub struct ValidationResult {
187    /// Whether the SQL is valid (no errors, warnings are allowed)
188    pub valid: bool,
189    /// List of validation errors and warnings
190    pub errors: Vec<ValidationError>,
191}
192
193impl ValidationResult {
194    /// Create a successful validation result
195    pub fn success() -> Self {
196        Self {
197            valid: true,
198            errors: Vec::new(),
199        }
200    }
201
202    /// Create a validation result with errors
203    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    /// Add an error to the result
214    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        // Parse invalid SQL and verify the error carries position info
253        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}