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