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