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
344impl From<LexerError> for Error {
345 fn from(value: LexerError) -> Self {
346 Self::Lexer(value)
347 }
348}
349
350impl From<ParserError> for Error {
351 fn from(value: ParserError) -> Self {
352 Self::Parser(value)
353 }
354}
355
356impl From<AnalysisError> for Error {
357 fn from(value: AnalysisError) -> Self {
358 Self::Analysis(value)
359 }
360}