Skip to main content

eventql_parser/
error.rs

1//! Error types for lexical analysis and parsing.
2//!
3//! This module defines the error types that can occur during tokenization
4//! and parsing of EventQL queries. All errors include position information
5//! (line and column numbers) to help diagnose issues in query strings.
6
7use crate::{Type, token::Symbol};
8use serde::Serialize;
9use thiserror::Error;
10
11/// Top-level error type for the EventQL parser.
12///
13/// This enum wraps both lexer and parser errors, providing a unified
14/// error type for the entire parsing pipeline.
15#[derive(Debug, Error, Serialize)]
16pub enum Error {
17    /// Error during lexical analysis (tokenization).
18    #[error(transparent)]
19    Lexer(LexerError),
20
21    /// Error during syntactic analysis (parsing).
22    #[error(transparent)]
23    Parser(ParserError),
24
25    /// Error during static analysis.
26    #[error(transparent)]
27    Analysis(AnalysisError),
28}
29
30/// Errors that can occur during lexical analysis.
31///
32/// These errors are produced by the tokenizer when the input string
33/// contains invalid characters or ends unexpectedly.
34#[derive(Debug, Error, Serialize)]
35pub enum LexerError {
36    /// The input ended unexpectedly while parsing a token.
37    ///
38    /// This typically occurs when a string literal or other multi-character
39    /// token is not properly closed.
40    #[error("unexpected end of input")]
41    IncompleteInput,
42
43    /// An invalid character was encountered at the specified position.
44    ///
45    /// The tuple contains `(line_number, column_number)`.
46    #[error("{0}:{1}: invalid character")]
47    InvalidSymbol(u32, u32),
48}
49
50/// Errors that can occur during syntactic analysis.
51///
52/// These errors are produced by the parser when the token sequence
53/// does not match the expected grammar of EventQL.
54#[derive(Debug, Error, Serialize)]
55pub enum ParserError {
56    /// Expected an identifier but found something else.
57    ///
58    /// Fields: `(line, column, found_token)`
59    #[error("{0}:{1}: expected identifier but got {2}")]
60    ExpectedIdent(u32, u32, String),
61
62    #[error("{0}:{1}: missing FROM statement")]
63    MissingFromStatement(u32, u32),
64
65    /// Expected a specific keyword but found something else.
66    ///
67    /// Fields: `(line, column, expected_keyword, found_token)`
68    #[error("{0}:{1}: expected keyword {2} but got {3}")]
69    ExpectedKeyword(u32, u32, &'static str, String),
70
71    /// Expected a specific symbol but found something else.
72    ///
73    /// Fields: `(line, column, expected_symbol, found_token)`
74    #[error("{0}:{1}: expected {2} but got {3}")]
75    ExpectedSymbol(u32, u32, Symbol, String),
76
77    /// An unexpected token was encountered.
78    ///
79    /// Fields: `(line, column, found_token)`
80    ///
81    /// This is a general error for tokens that don't fit the current parse context.
82    #[error("{0}:{1}: unexpected token {2}")]
83    UnexpectedToken(u32, u32, String),
84
85    /// Expected a type name but found something else.
86    ///
87    /// Fields: `(line, column, found_token)`
88    ///
89    /// This occurs when defining a type conversion operation but the left side is
90    /// not a type.
91    #[error("{0}:{1}: expected a type")]
92    ExpectedType(u32, u32),
93
94    /// The input ended unexpectedly while parsing.
95    ///
96    /// This occurs when the parser expects more tokens but encounters
97    /// the end of the token stream.
98    #[error("unexpected end of file")]
99    UnexpectedEof,
100}
101
102/// Errors that can occur during static analysis.
103///
104/// These errors are produced by the type checker when it encounters
105/// type mismatches, undeclared variables, or other semantic issues
106/// in the query.
107#[derive(Debug, Error, Serialize)]
108pub enum AnalysisError {
109    /// A binding with the same name already exists in the current scope.
110    ///
111    /// Fields: `(line, column, binding_name)`
112    ///
113    /// This occurs when trying to declare a variable that shadows an existing
114    /// binding in the same scope, such as using the same alias for multiple
115    /// FROM sources.
116    #[error("{0}:{1}: binding '{2}' already exists")]
117    BindingAlreadyExists(u32, u32, String),
118
119    /// A variable was referenced but not declared in any accessible scope.
120    ///
121    /// Fields: `(line, column, variable_name)`
122    ///
123    /// This occurs when referencing a variable that hasn't been bound by a
124    /// FROM clause or defined in the default scope.
125    #[error("{0}:{1}: variable '{2}' is undeclared")]
126    VariableUndeclared(u32, u32, String),
127
128    /// A type mismatch occurred between expected and actual types.
129    ///
130    /// Fields: `(line, column, expected_type, actual_type)`
131    ///
132    /// This occurs when an expression has a different type than what is
133    /// required by its context (e.g., using a string where a number is expected).
134    #[error("{0}:{1}: type mismatch: expected {2} but got {3} ")]
135    TypeMismatch(u32, u32, Type, Type),
136
137    /// A record field was accessed but doesn't exist in the record type.
138    ///
139    /// Fields: `(line, column, field_name)`
140    ///
141    /// This occurs when trying to access a field that is not defined in the
142    /// record's type definition.
143    #[error("{0}:{1}: record field '{2}' is undeclared ")]
144    FieldUndeclared(u32, u32, String),
145
146    /// A function was called but is not declared in the scope.
147    ///
148    /// Fields: `(line, column, function_name)`
149    ///
150    /// This occurs when calling a function that is not defined in the default
151    /// scope or any accessible scope.
152    #[error("{0}:{1}: function '{2}' is undeclared ")]
153    FuncUndeclared(u32, u32, String),
154
155    /// Expected a record type but found a different type.
156    ///
157    /// Fields: `(line, column, actual_type)`
158    ///
159    /// This occurs when a record type is required (e.g., for field access)
160    /// but a different type was found.
161    #[error("{0}:{1}: expected record but got {2}")]
162    ExpectRecord(u32, u32, Type),
163
164    /// Expected a record or sourced-property but found a different type.
165    ///
166    /// Fields: `(line, column, actual_type)`
167    ///
168    /// This occurs when checking a projection and the static analysis found
169    /// out the project into clause doesn't return a record nor a sourced-based property.
170    #[error("{0}:{1}: expected a record or a sourced-property but got {2}")]
171    ExpectRecordOrSourcedProperty(u32, u32, Type),
172
173    /// Expected an array type but found a different type.
174    ///
175    /// Fields: `(line, column, actual_type)`
176    ///
177    /// This occurs when an array type is required but a different type was found.
178    #[error("{0}:{1}: expected an array but got {2}")]
179    ExpectArray(u32, u32, Type),
180
181    /// Expected a field literal but found a different expression.
182    ///
183    /// Fields: `(line, column)`
184    ///
185    /// This occurs in contexts where only a simple field reference is allowed,
186    /// such as in GROUP BY or ORDER BY clauses.
187    #[error("{0}:{1}: expected a field")]
188    ExpectFieldLiteral(u32, u32),
189
190    /// Expected a record literal but found a different expression.
191    ///
192    /// Fields: `(line, column)`
193    ///
194    /// This occurs when a record literal is required, such as in the
195    /// PROJECT INTO clause.
196    #[error("{0}:{1}: expected a record")]
197    ExpectRecordLiteral(u32, u32),
198
199    /// When a custom type (meaning a type not supported by EventQL by default) is used but
200    /// not registered in the `AnalysisOptions` custom type set.
201    #[error("{0}:{1}: unsupported custom type '{2}'")]
202    UnsupportedCustomType(u32, u32, String),
203
204    /// A function was called with the wrong number of arguments.
205    ///
206    /// Fields: `(line, column, function_name)`
207    ///
208    /// This occurs when calling a function with a different number of arguments
209    /// than what the function signature requires.
210    #[error("{0}:{1}: incorrect number of arguments supplied to function '{2}'")]
211    FunWrongArgumentCount(u32, u32, String),
212
213    /// An aggregate function was used outside of a PROJECT INTO clause.
214    ///
215    /// Fields: `(line, column, function_name)`
216    ///
217    /// This occurs when an aggregate function (e.g., SUM, COUNT, AVG) is used
218    /// in a context where aggregation is not allowed, such as in WHERE, GROUP BY,
219    /// or ORDER BY clauses. Aggregate functions can only be used in the PROJECT INTO
220    /// clause to compute aggregated values over groups of events.
221    ///
222    /// # Example
223    ///
224    /// Invalid usage:
225    /// ```eql
226    /// FROM e IN events
227    /// WHERE COUNT() > 5  // Error: aggregate function in WHERE clause
228    /// PROJECT INTO e
229    /// ```
230    ///
231    /// Valid usage:
232    /// ```eql
233    /// FROM e IN events
234    /// PROJECT INTO { total: COUNT() }
235    /// ```
236    #[error("{0}:{1}: aggregate function '{2}' can only be used in a PROJECT INTO clause")]
237    WrongAggFunUsage(u32, u32, String),
238
239    /// An aggregate function was used together with source-bound fields.
240    ///
241    /// Fields: `(line, column)`
242    ///
243    /// This occurs when attempting to mix aggregate functions with fields that are
244    /// bound to source events within the same projection field. Aggregate functions
245    /// operate on groups of events, while source-bound fields refer to individual
246    /// event properties. These cannot be mixed in a single field expression.
247    ///
248    /// # Example
249    ///
250    /// Invalid usage:
251    /// ```eql
252    /// FROM e IN events
253    /// // Error: mixing aggregate (SUM) with source field (e.id)
254    /// PROJECT INTO { count: SUM(e.data.price), id: e.id }
255    /// ```
256    ///
257    /// Valid usage:
258    /// ```eql
259    /// FROM e IN events
260    /// PROJECT INTO { sum: SUM(e.data.price), label: "total" }
261    /// ```
262    #[error("{0}:{1}: aggregate functions cannot be used with source-bound fields")]
263    UnallowedAggFuncUsageWithSrcField(u32, u32),
264
265    /// An empty record literal was used in a context where it is not allowed.
266    ///
267    /// Fields: `(line, column)`
268    ///
269    /// This occurs when using an empty record `{}` as a projection, which would
270    /// result in a query that produces no output fields. Projections must contain
271    /// at least one field.
272    ///
273    /// # Example
274    ///
275    /// Invalid usage:
276    /// ```eql
277    /// FROM e IN events
278    /// PROJECT INTO {}  // Error: empty record
279    /// ```
280    ///
281    /// Valid usage:
282    /// ```eql
283    /// FROM e IN events
284    /// PROJECT INTO { id: e.id }
285    /// ```
286    #[error("{0}:{1}: unexpected empty record")]
287    EmptyRecord(u32, u32),
288
289    /// An aggregate function was called with an argument that is not a source-bound field.
290    ///
291    /// Fields: `(line, column)`
292    ///
293    /// This occurs when an aggregate function (e.g., SUM, COUNT, AVG) is called with
294    /// an argument that is not derived from source event properties. Aggregate functions
295    /// must operate on fields that come from the source events being queried, not on
296    /// constants, literals, or results from other functions.
297    ///
298    /// # Example
299    ///
300    /// Invalid usage:
301    /// ```eql
302    /// FROM e IN events
303    /// // Error: RAND() is constant value
304    /// PROJECT INTO { sum: SUM(RAND()) }
305    /// ```
306    ///
307    /// Valid usage:
308    /// ```eql
309    /// FROM e IN events
310    /// PROJECT INTO { sum: SUM(e.data.price) }
311    /// ```
312    #[error("{0}:{1}: aggregate functions arguments must be source-bound fields")]
313    ExpectSourceBoundProperty(u32, u32),
314
315    /// A constant expression is used in PROJECT INTO clause.
316    ///
317    /// Fields: `(line, column)`
318    ///
319    /// # Example
320    ///
321    /// Invalid usage:
322    /// ```eql
323    /// FROM e IN events
324    /// // Error: NOW() is constant value
325    /// PROJECT INTO NOW()
326    ///
327    /// ```
328    /// Invalid usage:
329    /// ```eql
330    /// FROM e IN events
331    /// // Error: DAY(NOW()) is also constant value
332    /// PROJECT INTO DAY(NOW())
333    /// ```
334    ///
335    /// Valid usage:
336    /// ```eql
337    /// FROM e IN events
338    /// PROJECT INTO DAY(e.data.date)
339    /// ```
340    #[error("{0}:{1}: constant expressions are forbidden in PROJECT INTO clause")]
341    ConstantExprInProjectIntoClause(u32, u32),
342
343    /// Expect an aggregate expression at this position in the query.
344    ///
345    /// Fields: `(line, column)`
346    ///
347    /// Invalid usage:
348    /// ```eql
349    /// FROM e IN events
350    /// GROUP BY e.data.department
351    /// // ERROR: the order by clause should use an aggregage expresion because GROUP Bys
352    /// // require an aggregate expression in this context.
353    /// ORDER BY e.data.salary
354    /// PROJECT INTO AVG(e.data.salary)
355    /// ```
356    /// Valid usage:
357    /// ```eql
358    /// FROM e IN events
359    /// GROUP BY e.data.department
360    /// ORDER BY AVG(e.data.salary)
361    /// PROJECT INTO AVG(e.data.salary)
362    /// ```
363    #[error("{0}:{1}: expect aggregate expression")]
364    ExpectAggExpr(u32, u32),
365}
366
367impl From<LexerError> for Error {
368    fn from(value: LexerError) -> Self {
369        Self::Lexer(value)
370    }
371}
372
373impl From<ParserError> for Error {
374    fn from(value: ParserError) -> Self {
375        Self::Parser(value)
376    }
377}
378
379impl From<AnalysisError> for Error {
380    fn from(value: AnalysisError) -> Self {
381        Self::Analysis(value)
382    }
383}