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}