1use std::fmt;
43use thiserror::Error;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum EvaluationErrorKind {
55 UndefinedFunction {
60 name: String,
62 },
63 ArgumentCount {
65 expected: Option<u32>,
67 actual: Option<u32>,
69 },
70 TypeError {
72 detail: String,
74 },
75 Other,
77}
78
79impl fmt::Display for EvaluationErrorKind {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 EvaluationErrorKind::UndefinedFunction { name } => {
83 write!(f, "undefined function '{}'", name)
84 }
85 EvaluationErrorKind::ArgumentCount {
86 expected: Some(exp),
87 actual: Some(act),
88 } => write!(f, "expected {} arguments, found {}", exp, act),
89 EvaluationErrorKind::ArgumentCount { .. } => write!(f, "wrong number of arguments"),
90 EvaluationErrorKind::TypeError { detail } => write!(f, "type error: {}", detail),
91 EvaluationErrorKind::Other => write!(f, "evaluation error"),
92 }
93 }
94}
95
96pub(crate) fn classify_evaluation_error(message: &str) -> EvaluationErrorKind {
104 if let Some(rest) = message
106 .strip_prefix("Runtime error: Call to undefined function ")
107 .or_else(|| message.strip_prefix("Call to undefined function "))
108 {
109 let name = rest.split([' ', '(']).next().unwrap_or(rest).to_string();
111 return EvaluationErrorKind::UndefinedFunction { name };
112 }
113
114 if message.contains("arguments: expected") {
117 let expected = extract_number_after(message, "expected ");
118 let actual = extract_number_after(message, "found ");
119 return EvaluationErrorKind::ArgumentCount { expected, actual };
120 }
121
122 if message.contains("expects type") {
124 let detail = message
125 .strip_prefix("Runtime error: ")
126 .unwrap_or(message)
127 .to_string();
128 return EvaluationErrorKind::TypeError { detail };
129 }
130
131 EvaluationErrorKind::Other
132}
133
134fn extract_number_after(s: &str, prefix: &str) -> Option<u32> {
136 let idx = s.find(prefix)?;
137 let after = &s[idx + prefix.len()..];
138 let num_str: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
139 num_str.parse().ok()
140}
141
142#[derive(Debug, Error)]
150#[non_exhaustive]
151pub enum EngineError {
152 #[error("Invalid expression: {0}")]
158 InvalidExpression(String),
159
160 #[error("Invalid JSON: {0}")]
165 InvalidJson(String),
166
167 #[error("Evaluation failed: {message}")]
172 EvaluationFailed {
173 message: String,
175 kind: EvaluationErrorKind,
177 },
178
179 #[error("Unknown function: {0}")]
183 UnknownFunction(String),
184
185 #[error("Query not found: {0}")]
190 QueryNotFound(String),
191
192 #[error("Registration failed: {0}")]
197 RegistrationFailed(String),
198
199 #[error("Config error: {0}")]
203 ConfigError(String),
204
205 #[error("Internal error: {0}")]
210 Internal(String),
211
212 #[cfg(feature = "arrow")]
216 #[error("Arrow error: {0}")]
217 ArrowError(String),
218}
219
220impl EngineError {
221 pub(crate) fn evaluation_failed(message: String) -> Self {
224 let kind = classify_evaluation_error(&message);
225 EngineError::EvaluationFailed { message, kind }
226 }
227}
228
229pub type Result<T> = std::result::Result<T, EngineError>;
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_classify_undefined_function() {
240 let kind = classify_evaluation_error(
241 "Runtime error: Call to undefined function foo_bar (line 0, column 7)",
242 );
243 assert_eq!(
244 kind,
245 EvaluationErrorKind::UndefinedFunction {
246 name: "foo_bar".to_string()
247 }
248 );
249 }
250
251 #[test]
252 fn test_classify_too_many_arguments() {
253 let kind = classify_evaluation_error(
254 "Runtime error: Too many arguments: expected 1, found 2 (line 0, column 6)",
255 );
256 assert_eq!(
257 kind,
258 EvaluationErrorKind::ArgumentCount {
259 expected: Some(1),
260 actual: Some(2)
261 }
262 );
263 }
264
265 #[test]
266 fn test_classify_not_enough_arguments() {
267 let kind = classify_evaluation_error(
268 "Runtime error: Not enough arguments: expected 2, found 1 (line 0, column 4)",
269 );
270 assert_eq!(
271 kind,
272 EvaluationErrorKind::ArgumentCount {
273 expected: Some(2),
274 actual: Some(1)
275 }
276 );
277 }
278
279 #[test]
280 fn test_classify_type_error() {
281 let kind = classify_evaluation_error(
282 "Runtime error: Argument 0 expects type array[number], given object (line 0, column 3)",
283 );
284 match kind {
285 EvaluationErrorKind::TypeError { detail } => {
286 assert!(detail.contains("expects type"));
287 }
288 other => panic!("Expected TypeError, got {:?}", other),
289 }
290 }
291
292 #[test]
293 fn test_classify_other() {
294 let kind = classify_evaluation_error("Some unknown error");
295 assert_eq!(kind, EvaluationErrorKind::Other);
296 }
297
298 #[test]
301 fn test_display_undefined_function() {
302 let kind = EvaluationErrorKind::UndefinedFunction {
303 name: "foo".to_string(),
304 };
305 assert_eq!(kind.to_string(), "undefined function 'foo'");
306 }
307
308 #[test]
309 fn test_display_argument_count_with_numbers() {
310 let kind = EvaluationErrorKind::ArgumentCount {
311 expected: Some(2),
312 actual: Some(3),
313 };
314 assert_eq!(kind.to_string(), "expected 2 arguments, found 3");
315 }
316
317 #[test]
318 fn test_display_argument_count_without_numbers() {
319 let kind = EvaluationErrorKind::ArgumentCount {
320 expected: None,
321 actual: None,
322 };
323 assert_eq!(kind.to_string(), "wrong number of arguments");
324 }
325
326 #[test]
327 fn test_display_type_error() {
328 let kind = EvaluationErrorKind::TypeError {
329 detail: "some detail".to_string(),
330 };
331 assert_eq!(kind.to_string(), "type error: some detail");
332 }
333
334 #[test]
335 fn test_display_other() {
336 let kind = EvaluationErrorKind::Other;
337 assert_eq!(kind.to_string(), "evaluation error");
338 }
339
340 #[test]
343 fn test_engine_error_display_invalid_expression() {
344 let err = EngineError::InvalidExpression("unexpected token".to_string());
345 assert_eq!(err.to_string(), "Invalid expression: unexpected token");
346 }
347
348 #[test]
349 fn test_engine_error_display_invalid_json() {
350 let err = EngineError::InvalidJson("expected value at line 1".to_string());
351 assert_eq!(err.to_string(), "Invalid JSON: expected value at line 1");
352 }
353
354 #[test]
355 fn test_engine_error_display_evaluation_failed() {
356 let err = EngineError::EvaluationFailed {
357 message: "something went wrong".to_string(),
358 kind: EvaluationErrorKind::Other,
359 };
360 assert_eq!(err.to_string(), "Evaluation failed: something went wrong");
361 }
362
363 #[test]
364 fn test_engine_error_display_all_variants() {
365 let unknown = EngineError::UnknownFunction("mystery".to_string());
366 assert_eq!(unknown.to_string(), "Unknown function: mystery");
367
368 let not_found = EngineError::QueryNotFound("my_query".to_string());
369 assert_eq!(not_found.to_string(), "Query not found: my_query");
370
371 let reg_failed = EngineError::RegistrationFailed("duplicate name".to_string());
372 assert_eq!(
373 reg_failed.to_string(),
374 "Registration failed: duplicate name"
375 );
376
377 let config = EngineError::ConfigError("missing field".to_string());
378 assert_eq!(config.to_string(), "Config error: missing field");
379
380 let internal = EngineError::Internal("lock poisoned".to_string());
381 assert_eq!(internal.to_string(), "Internal error: lock poisoned");
382 }
383
384 #[test]
387 fn test_evaluation_failed_constructor() {
388 let err = EngineError::evaluation_failed("Call to undefined function foo".to_string());
389 match err {
390 EngineError::EvaluationFailed {
391 ref message,
392 ref kind,
393 } => {
394 assert_eq!(message, "Call to undefined function foo");
395 assert_eq!(
396 *kind,
397 EvaluationErrorKind::UndefinedFunction {
398 name: "foo".to_string()
399 }
400 );
401 }
402 other => panic!("Expected EvaluationFailed, got {:?}", other),
403 }
404 }
405}