vibesql/error/
mod.rs

1//! Error handling for the VibeSQL parser and analyzer.
2//!
3//! This module provides error types and utilities for representing and
4//! displaying parsing and analysis errors with source location information.
5
6use std::fmt;
7
8/// A span in the source code, represented as byte offsets.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
10pub struct Span {
11    /// Start byte offset (inclusive)
12    pub start: usize,
13    /// End byte offset (exclusive)
14    pub end: usize,
15}
16
17impl Span {
18    /// Create a new span from start and end offsets.
19    pub fn new(start: usize, end: usize) -> Self {
20        Self { start, end }
21    }
22
23    /// Create a span for a single position.
24    pub fn point(pos: usize) -> Self {
25        Self {
26            start: pos,
27            end: pos + 1,
28        }
29    }
30
31    /// Create an empty span at a position.
32    pub fn empty(pos: usize) -> Self {
33        Self {
34            start: pos,
35            end: pos,
36        }
37    }
38
39    /// Merge two spans into one that covers both.
40    pub fn merge(self, other: Span) -> Span {
41        Span {
42            start: self.start.min(other.start),
43            end: self.end.max(other.end),
44        }
45    }
46
47    /// Get the length of this span in bytes.
48    pub fn len(&self) -> usize {
49        self.end.saturating_sub(self.start)
50    }
51
52    /// Check if this span is empty.
53    pub fn is_empty(&self) -> bool {
54        self.start >= self.end
55    }
56}
57
58/// The kind of error that occurred.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum ErrorKind {
61    // Lexer errors
62    UnexpectedCharacter(char),
63    UnterminatedString,
64    UnterminatedBlockComment,
65    InvalidEscapeSequence(String),
66    InvalidNumber(String),
67    InvalidHexLiteral,
68    InvalidBytesLiteral,
69
70    // Parser errors
71    UnexpectedToken {
72        expected: String,
73        found: String,
74    },
75    UnexpectedEof,
76    ExpectedExpression,
77    ExpectedIdentifier,
78    ExpectedKeyword(String),
79    InvalidSyntax(String),
80    UnsupportedFeature(String),
81
82    // Analyzer errors
83    UndefinedColumn(String),
84    UndefinedTable(String),
85    UndefinedFunction(String),
86    AmbiguousColumn(String),
87    TypeMismatch {
88        expected: String,
89        found: String,
90    },
91    InvalidArgumentCount {
92        function: String,
93        expected: usize,
94        found: usize,
95    },
96    DuplicateColumn(String),
97    DuplicateAlias(String),
98    InvalidGroupBy(String),
99    InvalidOrderBy(String),
100    InvalidAggregateUsage(String),
101    InvalidWindowFunction(String),
102
103    // General errors
104    Internal(String),
105}
106
107impl fmt::Display for ErrorKind {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            // Lexer errors
111            ErrorKind::UnexpectedCharacter(c) => write!(f, "unexpected character '{}'", c),
112            ErrorKind::UnterminatedString => write!(f, "unterminated string literal"),
113            ErrorKind::UnterminatedBlockComment => write!(f, "unterminated block comment"),
114            ErrorKind::InvalidEscapeSequence(s) => write!(f, "invalid escape sequence '{}'", s),
115            ErrorKind::InvalidNumber(s) => write!(f, "invalid number '{}'", s),
116            ErrorKind::InvalidHexLiteral => write!(f, "invalid hexadecimal literal"),
117            ErrorKind::InvalidBytesLiteral => write!(f, "invalid bytes literal"),
118
119            // Parser errors
120            ErrorKind::UnexpectedToken { expected, found } => {
121                write!(f, "expected {}, found {}", expected, found)
122            }
123            ErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
124            ErrorKind::ExpectedExpression => write!(f, "expected expression"),
125            ErrorKind::ExpectedIdentifier => write!(f, "expected identifier"),
126            ErrorKind::ExpectedKeyword(kw) => write!(f, "expected keyword '{}'", kw),
127            ErrorKind::InvalidSyntax(msg) => write!(f, "invalid syntax: {}", msg),
128            ErrorKind::UnsupportedFeature(feat) => write!(f, "unsupported feature: {}", feat),
129
130            // Analyzer errors
131            ErrorKind::UndefinedColumn(name) => write!(f, "undefined column '{}'", name),
132            ErrorKind::UndefinedTable(name) => write!(f, "undefined table '{}'", name),
133            ErrorKind::UndefinedFunction(name) => write!(f, "undefined function '{}'", name),
134            ErrorKind::AmbiguousColumn(name) => write!(f, "ambiguous column reference '{}'", name),
135            ErrorKind::TypeMismatch { expected, found } => {
136                write!(f, "type mismatch: expected {}, found {}", expected, found)
137            }
138            ErrorKind::InvalidArgumentCount {
139                function,
140                expected,
141                found,
142            } => {
143                write!(
144                    f,
145                    "function '{}' expects {} arguments, found {}",
146                    function, expected, found
147                )
148            }
149            ErrorKind::DuplicateColumn(name) => write!(f, "duplicate column '{}'", name),
150            ErrorKind::DuplicateAlias(name) => write!(f, "duplicate alias '{}'", name),
151            ErrorKind::InvalidGroupBy(msg) => write!(f, "invalid GROUP BY: {}", msg),
152            ErrorKind::InvalidOrderBy(msg) => write!(f, "invalid ORDER BY: {}", msg),
153            ErrorKind::InvalidAggregateUsage(msg) => write!(f, "invalid aggregate usage: {}", msg),
154            ErrorKind::InvalidWindowFunction(msg) => write!(f, "invalid window function: {}", msg),
155
156            // General errors
157            ErrorKind::Internal(msg) => write!(f, "internal error: {}", msg),
158        }
159    }
160}
161
162/// An error with source location information.
163#[derive(Debug, Clone)]
164pub struct Error {
165    /// The kind of error.
166    pub kind: ErrorKind,
167    /// The source span where the error occurred.
168    span: Option<Span>,
169    /// Optional context message.
170    context: Option<String>,
171}
172
173impl Error {
174    /// Create a new error with the given kind.
175    pub fn new(kind: ErrorKind) -> Self {
176        Self {
177            kind,
178            span: None,
179            context: None,
180        }
181    }
182
183    /// Create a new error with source location.
184    pub fn with_span(kind: ErrorKind, span: Span) -> Self {
185        Self {
186            kind,
187            span: Some(span),
188            context: None,
189        }
190    }
191
192    /// Add context to this error.
193    pub fn with_context(mut self, context: impl Into<String>) -> Self {
194        self.context = Some(context.into());
195        self
196    }
197
198    /// Get the span of this error, if any.
199    pub fn span(&self) -> Option<Span> {
200        self.span
201    }
202
203    /// Get the error kind.
204    pub fn kind(&self) -> &ErrorKind {
205        &self.kind
206    }
207
208    // Convenience constructors for common errors
209    pub fn unexpected_char(c: char, pos: usize) -> Self {
210        Self::with_span(ErrorKind::UnexpectedCharacter(c), Span::point(pos))
211    }
212
213    pub fn unexpected_token(
214        expected: impl Into<String>,
215        found: impl Into<String>,
216        span: Span,
217    ) -> Self {
218        Self::with_span(
219            ErrorKind::UnexpectedToken {
220                expected: expected.into(),
221                found: found.into(),
222            },
223            span,
224        )
225    }
226
227    pub fn unexpected_eof(pos: usize) -> Self {
228        Self::with_span(ErrorKind::UnexpectedEof, Span::point(pos))
229    }
230
231    pub fn expected_expression(span: Span) -> Self {
232        Self::with_span(ErrorKind::ExpectedExpression, span)
233    }
234
235    pub fn expected_identifier(span: Span) -> Self {
236        Self::with_span(ErrorKind::ExpectedIdentifier, span)
237    }
238
239    pub fn expected_keyword(keyword: impl Into<String>, span: Span) -> Self {
240        Self::with_span(ErrorKind::ExpectedKeyword(keyword.into()), span)
241    }
242
243    pub fn invalid_syntax(msg: impl Into<String>, span: Span) -> Self {
244        Self::with_span(ErrorKind::InvalidSyntax(msg.into()), span)
245    }
246
247    pub fn unsupported(feature: impl Into<String>, span: Span) -> Self {
248        Self::with_span(ErrorKind::UnsupportedFeature(feature.into()), span)
249    }
250
251    pub fn unterminated_string(span: Span) -> Self {
252        Self::with_span(ErrorKind::UnterminatedString, span)
253    }
254
255    pub fn unterminated_comment(span: Span) -> Self {
256        Self::with_span(ErrorKind::UnterminatedBlockComment, span)
257    }
258
259    pub fn invalid_escape(seq: impl Into<String>, span: Span) -> Self {
260        Self::with_span(ErrorKind::InvalidEscapeSequence(seq.into()), span)
261    }
262
263    pub fn invalid_number(num: impl Into<String>, span: Span) -> Self {
264        Self::with_span(ErrorKind::InvalidNumber(num.into()), span)
265    }
266
267    /// Create an analyzer error.
268    pub fn analyzer(msg: impl Into<String>) -> Self {
269        Self::new(ErrorKind::Internal(msg.into()))
270    }
271}
272
273impl fmt::Display for Error {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        write!(f, "{}", self.kind)?;
276        if let Some(ref ctx) = self.context {
277            write!(f, " ({})", ctx)?;
278        }
279        if let Some(span) = self.span {
280            write!(f, " at position {}", span.start)?;
281        }
282        Ok(())
283    }
284}
285
286impl std::error::Error for Error {}
287
288/// A specialized Result type for VibeSQL operations.
289pub type Result<T> = std::result::Result<T, Error>;
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_span_merge() {
297        let span1 = Span::new(10, 20);
298        let span2 = Span::new(15, 30);
299        let merged = span1.merge(span2);
300        assert_eq!(merged.start, 10);
301        assert_eq!(merged.end, 30);
302    }
303
304    #[test]
305    fn test_error_display() {
306        let err = Error::unexpected_char('$', 5);
307        let msg = format!("{}", err);
308        assert!(msg.contains("unexpected character"));
309        assert!(msg.contains("$"));
310    }
311
312    #[test]
313    fn test_error_with_context() {
314        let err = Error::new(ErrorKind::UnexpectedEof).with_context("parsing SELECT clause");
315        let msg = format!("{}", err);
316        assert!(msg.contains("parsing SELECT clause"));
317    }
318}