0
# Error Handling
1
2
Hierarchical exception system covering syntax errors, evaluation errors, type mismatches, and symbol resolution failures. The error handling system provides detailed error information, suggestions for fixes, and proper exception hierarchies for comprehensive error management.
3
4
## Capabilities
5
6
### Base Exception Classes
7
8
The foundation exception classes that all rule engine errors inherit from.
9
10
```python { .api }
11
class EngineError(Exception):
12
"""Base exception class for all rule engine errors."""
13
14
def __init__(self, message: str = ''):
15
"""
16
Initialize the engine error.
17
18
Args:
19
message (str): Description of the error
20
"""
21
22
@property
23
def message(self) -> str:
24
"""The error message description."""
25
```
26
27
**Usage Example:**
28
29
```python
30
import rule_engine
31
32
try:
33
# Any rule engine operation
34
rule = rule_engine.Rule('invalid syntax ===')
35
except rule_engine.EngineError as e:
36
print(f"Rule engine error: {e.message}")
37
print(f"Error type: {type(e).__name__}")
38
```
39
40
### Evaluation Errors
41
42
Errors that occur during rule evaluation and expression processing.
43
44
```python { .api }
45
class EvaluationError(EngineError):
46
"""
47
Errors that occur during rule evaluation, including type mismatches,
48
function call failures, and expression evaluation issues.
49
"""
50
```
51
52
**Usage Example:**
53
54
```python
55
import rule_engine
56
57
try:
58
rule = rule_engine.Rule('name + age') # Can't add string + number
59
rule.evaluate({'name': 'John', 'age': 25})
60
except rule_engine.EvaluationError as e:
61
print(f"Evaluation failed: {e.message}")
62
```
63
64
### Syntax Errors
65
66
Errors related to rule expression syntax and parsing.
67
68
```python { .api }
69
class RuleSyntaxError(EngineError):
70
"""Errors in rule expression grammar and syntax."""
71
72
def __init__(self, message: str, token=None):
73
"""
74
Initialize syntax error.
75
76
Args:
77
message (str): Description of the syntax error
78
token: PLY token related to the error (if available)
79
"""
80
81
@property
82
def token(self):
83
"""The PLY token related to the syntax error."""
84
```
85
86
**Usage Example:**
87
88
```python
89
import rule_engine
90
91
try:
92
rule = rule_engine.Rule('name == "John" and and age > 18') # Double 'and'
93
except rule_engine.RuleSyntaxError as e:
94
print(f"Syntax error: {e.message}")
95
if e.token:
96
print(f"Error at line {e.token.lineno}, position {e.token.lexpos}")
97
98
# Common syntax errors
99
syntax_errors = [
100
'name === "value"', # Invalid operator
101
'if (condition', # Missing closing parenthesis
102
'name == "unclosed', # Unclosed string
103
'3.14.15', # Invalid float format
104
'regex =~ "[unclosed', # Invalid regex
105
]
106
107
for expr in syntax_errors:
108
try:
109
rule_engine.Rule(expr)
110
except rule_engine.RuleSyntaxError as e:
111
print(f"'{expr}' -> {e.message}")
112
```
113
114
## Symbol Resolution Errors
115
116
Errors related to resolving symbols and accessing object members.
117
118
### Symbol Resolution Error
119
120
Raised when a symbol cannot be resolved to a value.
121
122
```python { .api }
123
class SymbolResolutionError(EvaluationError):
124
"""Error when a symbol name cannot be resolved."""
125
126
def __init__(self, symbol_name: str, symbol_scope: str = None,
127
thing=UNDEFINED, suggestion: str = None):
128
"""
129
Initialize symbol resolution error.
130
131
Args:
132
symbol_name (str): Name of the unresolved symbol
133
symbol_scope (str): Scope where symbol should be valid
134
thing: Root object used for resolution
135
suggestion (str): Optional suggestion for correct symbol name
136
"""
137
138
@property
139
def symbol_name(self) -> str:
140
"""The name of the symbol that couldn't be resolved."""
141
142
@property
143
def symbol_scope(self) -> str:
144
"""The scope where the symbol should be valid."""
145
146
@property
147
def thing(self):
148
"""The root object used for symbol resolution."""
149
150
@property
151
def suggestion(self) -> str:
152
"""Optional suggestion for a correct symbol name."""
153
```
154
155
**Usage Example:**
156
157
```python
158
import rule_engine
159
160
try:
161
rule = rule_engine.Rule('unknown_field == "value"')
162
rule.matches({'known_field': 'value'})
163
except rule_engine.SymbolResolutionError as e:
164
print(f"Unknown symbol: '{e.symbol_name}'")
165
if e.suggestion:
166
print(f"Did you mean: '{e.suggestion}'")
167
if e.thing is not rule_engine.UNDEFINED:
168
available = list(e.thing.keys()) if hasattr(e.thing, 'keys') else dir(e.thing)
169
print(f"Available symbols: {available}")
170
```
171
172
### Attribute Resolution Error
173
174
Raised when an object attribute cannot be accessed.
175
176
```python { .api }
177
class AttributeResolutionError(EvaluationError):
178
"""Error when an attribute cannot be resolved on an object."""
179
180
def __init__(self, attribute_name: str, object_, thing=UNDEFINED, suggestion: str = None):
181
"""
182
Initialize attribute resolution error.
183
184
Args:
185
attribute_name (str): Name of the unresolved attribute
186
object_: Object where attribute access was attempted
187
thing: Root object used for resolution
188
suggestion (str): Optional suggestion for correct attribute name
189
"""
190
191
@property
192
def attribute_name(self) -> str:
193
"""The name of the attribute that couldn't be resolved."""
194
195
@property
196
def object(self):
197
"""The object where attribute access was attempted."""
198
199
@property
200
def thing(self):
201
"""The root object used for resolution."""
202
203
@property
204
def suggestion(self) -> str:
205
"""Optional suggestion for a correct attribute name."""
206
```
207
208
**Usage Example:**
209
210
```python
211
import rule_engine
212
213
class Person:
214
def __init__(self, name):
215
self.name = name
216
self.first_name = name.split()[0]
217
218
try:
219
context = rule_engine.Context(resolver=rule_engine.resolve_attribute)
220
rule = rule_engine.Rule('firstname', context=context) # Typo: should be 'first_name'
221
rule.evaluate(Person('John Doe'))
222
except rule_engine.AttributeResolutionError as e:
223
print(f"Attribute '{e.attribute_name}' not found")
224
if e.suggestion:
225
print(f"Did you mean: '{e.suggestion}'")
226
print(f"Available attributes: {[attr for attr in dir(e.object) if not attr.startswith('_')]}")
227
```
228
229
## Type-Related Errors
230
231
Errors related to type mismatches and type validation.
232
233
### Symbol Type Error
234
235
Raised when a symbol resolves to a value of the wrong type.
236
237
```python { .api }
238
class SymbolTypeError(EvaluationError):
239
"""Error when a symbol resolves to an incompatible type."""
240
241
def __init__(self, symbol_name: str, is_value, is_type, expected_type):
242
"""
243
Initialize symbol type error.
244
245
Args:
246
symbol_name (str): Name of the symbol with wrong type
247
is_value: The actual Python value
248
is_type: The actual DataType
249
expected_type: The expected DataType
250
"""
251
252
@property
253
def symbol_name(self) -> str:
254
"""The name of the symbol with incorrect type."""
255
256
@property
257
def is_value(self):
258
"""The actual Python value of the symbol."""
259
260
@property
261
def is_type(self):
262
"""The actual DataType of the symbol."""
263
264
@property
265
def expected_type(self):
266
"""The expected DataType for the symbol."""
267
```
268
269
### Attribute Type Error
270
271
Raised when an attribute resolves to a value of the wrong type.
272
273
```python { .api }
274
class AttributeTypeError(EvaluationError):
275
"""Error when an attribute resolves to an incompatible type."""
276
277
def __init__(self, attribute_name: str, object_type, is_value, is_type, expected_type):
278
"""
279
Initialize attribute type error.
280
281
Args:
282
attribute_name (str): Name of the attribute with wrong type
283
object_type: The object type where attribute was resolved
284
is_value: The actual Python value
285
is_type: The actual DataType
286
expected_type: The expected DataType
287
"""
288
289
@property
290
def attribute_name(self) -> str:
291
"""The name of the attribute with incorrect type."""
292
293
@property
294
def object_type(self):
295
"""The object on which the attribute was resolved."""
296
297
@property
298
def is_value(self):
299
"""The actual Python value of the attribute."""
300
301
@property
302
def is_type(self):
303
"""The actual DataType of the attribute."""
304
305
@property
306
def expected_type(self):
307
"""The expected DataType for the attribute."""
308
```
309
310
**Usage Example:**
311
312
```python
313
import rule_engine
314
315
# Type error example
316
type_map = {
317
'age': rule_engine.DataType.FLOAT,
318
'name': rule_engine.DataType.STRING
319
}
320
321
context = rule_engine.Context(type_resolver=rule_engine.type_resolver_from_dict(type_map))
322
323
try:
324
# This should work
325
rule = rule_engine.Rule('age > 18', context=context)
326
result = rule.matches({'age': 25, 'name': 'John'})
327
328
# This will cause a type error if age is provided as string
329
result = rule.matches({'age': "twenty-five", 'name': 'John'})
330
except rule_engine.SymbolTypeError as e:
331
print(f"Type error for symbol '{e.symbol_name}':")
332
print(f" Expected: {e.expected_type.name}")
333
print(f" Got: {e.is_type.name} ({e.is_value})")
334
```
335
336
## Specialized Syntax Errors
337
338
Specific syntax errors for different data types and constructs.
339
340
### Data Type Syntax Errors
341
342
```python { .api }
343
class BytesSyntaxError(EngineError):
344
"""Error in bytes literal syntax."""
345
346
def __init__(self, message: str, value: str):
347
"""
348
Args:
349
message (str): Error description
350
value (str): The malformed bytes value
351
"""
352
353
@property
354
def value(self) -> str:
355
"""The bytes value that caused the error."""
356
357
class StringSyntaxError(EngineError):
358
"""Error in string literal syntax."""
359
360
def __init__(self, message: str, value: str):
361
"""
362
Args:
363
message (str): Error description
364
value (str): The malformed string value
365
"""
366
367
@property
368
def value(self) -> str:
369
"""The string value that caused the error."""
370
371
class DatetimeSyntaxError(EngineError):
372
"""Error in datetime literal syntax."""
373
374
def __init__(self, message: str, value: str):
375
"""
376
Args:
377
message (str): Error description
378
value (str): The malformed datetime value
379
"""
380
381
@property
382
def value(self) -> str:
383
"""The datetime value that caused the error."""
384
385
class FloatSyntaxError(EngineError):
386
"""Error in float literal syntax."""
387
388
def __init__(self, message: str, value: str):
389
"""
390
Args:
391
message (str): Error description
392
value (str): The malformed float value
393
"""
394
395
@property
396
def value(self) -> str:
397
"""The float value that caused the error."""
398
399
class TimedeltaSyntaxError(EngineError):
400
"""Error in timedelta literal syntax."""
401
402
def __init__(self, message: str, value: str):
403
"""
404
Args:
405
message (str): Error description
406
value (str): The malformed timedelta value
407
"""
408
409
@property
410
def value(self) -> str:
411
"""The timedelta value that caused the error."""
412
413
class RegexSyntaxError(EngineError):
414
"""Error in regular expression syntax."""
415
416
def __init__(self, message: str, error, value: str):
417
"""
418
Args:
419
message (str): Error description
420
error: The original re.error exception
421
value (str): The malformed regex pattern
422
"""
423
424
@property
425
def error(self):
426
"""The original re.error exception."""
427
428
@property
429
def value(self) -> str:
430
"""The regex pattern that caused the error."""
431
```
432
433
**Usage Example:**
434
435
```python
436
import rule_engine
437
438
# Examples of syntax errors for different data types
439
syntax_test_cases = [
440
('dt"2023-13-45"', rule_engine.DatetimeSyntaxError), # Invalid date
441
('3.14.15.9', rule_engine.FloatSyntaxError), # Invalid float
442
('td"25:70:80"', rule_engine.TimedeltaSyntaxError), # Invalid timedelta
443
('b"\\xGH"', rule_engine.BytesSyntaxError), # Invalid bytes escape
444
('"unclosed string', rule_engine.StringSyntaxError), # Unclosed string
445
('name =~ "[unclosed"', rule_engine.RegexSyntaxError), # Invalid regex
446
]
447
448
for expr, expected_error in syntax_test_cases:
449
try:
450
rule_engine.Rule(expr)
451
except expected_error as e:
452
print(f"'{expr}' -> {type(e).__name__}: {e.message}")
453
if hasattr(e, 'value'):
454
print(f" Problematic value: '{e.value}'")
455
```
456
457
## Other Specialized Errors
458
459
### Function Call Error
460
461
Errors that occur when calling functions within expressions.
462
463
```python { .api }
464
class FunctionCallError(EvaluationError):
465
"""Error during function execution."""
466
467
def __init__(self, message: str, error=None, function_name: str = None):
468
"""
469
Args:
470
message (str): Error description
471
error: The original exception that caused this error
472
function_name (str): Name of the function that failed
473
"""
474
475
@property
476
def error(self):
477
"""The original exception that caused this error."""
478
479
@property
480
def function_name(self) -> str:
481
"""The name of the function that failed."""
482
```
483
484
### Lookup Error
485
486
Errors that occur during container lookups (similar to IndexError/KeyError).
487
488
```python { .api }
489
class LookupError(EvaluationError):
490
"""Error when lookup operations fail on containers."""
491
492
def __init__(self, container, item):
493
"""
494
Args:
495
container: The container object where lookup failed
496
item: The key/index that was used for lookup
497
"""
498
499
@property
500
def container(self):
501
"""The container where the lookup failed."""
502
503
@property
504
def item(self):
505
"""The key/index used for the failed lookup."""
506
```
507
508
**Usage Example:**
509
510
```python
511
import rule_engine
512
513
# Function call errors
514
try:
515
rule = rule_engine.Rule('unknown_function(42)')
516
rule.evaluate({})
517
except rule_engine.FunctionCallError as e:
518
print(f"Function error: {e.message}")
519
if e.function_name:
520
print(f"Failed function: {e.function_name}")
521
522
# Lookup errors
523
try:
524
rule = rule_engine.Rule('data[missing_key]')
525
rule.evaluate({'data': {'existing_key': 'value'}})
526
except rule_engine.LookupError as e:
527
print(f"Lookup failed: container={type(e.container)}, item={e.item}")
528
```
529
530
## Error Handling Best Practices
531
532
### Comprehensive Error Handling
533
534
```python
535
import rule_engine
536
537
def safe_rule_evaluation(rule_text, data, context=None):
538
"""Safely evaluate a rule with comprehensive error handling."""
539
try:
540
rule = rule_engine.Rule(rule_text, context=context)
541
return rule.matches(data), None
542
except rule_engine.RuleSyntaxError as e:
543
return False, f"Syntax error: {e.message}"
544
except rule_engine.SymbolResolutionError as e:
545
error_msg = f"Unknown symbol: {e.symbol_name}"
546
if e.suggestion:
547
error_msg += f" (did you mean: {e.suggestion}?)"
548
return False, error_msg
549
except rule_engine.EvaluationError as e:
550
return False, f"Evaluation error: {e.message}"
551
except rule_engine.EngineError as e:
552
return False, f"Rule engine error: {e.message}"
553
554
# Usage
555
result, error = safe_rule_evaluation('age > 18', {'age': 25})
556
if error:
557
print(f"Error: {error}")
558
else:
559
print(f"Result: {result}")
560
```
561
562
### Error Logging and Debugging
563
564
```python
565
import rule_engine
566
import logging
567
568
logger = logging.getLogger(__name__)
569
570
def debug_rule_execution(rule_text, data, context=None):
571
"""Execute rule with detailed error logging."""
572
try:
573
logger.info(f"Evaluating rule: {rule_text}")
574
rule = rule_engine.Rule(rule_text, context=context)
575
result = rule.matches(data)
576
logger.info(f"Rule evaluation successful: {result}")
577
return result
578
except rule_engine.RuleSyntaxError as e:
579
logger.error(f"Syntax error in rule '{rule_text}': {e.message}")
580
if e.token:
581
logger.error(f"Error location: line {e.token.lineno}, pos {e.token.lexpos}")
582
raise
583
except rule_engine.SymbolResolutionError as e:
584
logger.error(f"Symbol resolution failed: {e.symbol_name}")
585
if e.suggestion:
586
logger.info(f"Suggested correction: {e.suggestion}")
587
available_symbols = list(data.keys()) if hasattr(data, 'keys') else dir(data)
588
logger.debug(f"Available symbols: {available_symbols}")
589
raise
590
except rule_engine.EvaluationError as e:
591
logger.error(f"Evaluation failed for rule '{rule_text}': {e.message}")
592
logger.debug(f"Data: {data}")
593
raise
594
```
595
596
### Graceful Degradation
597
598
```python
599
import rule_engine
600
601
class RuleProcessor:
602
"""Process rules with fallback strategies."""
603
604
def __init__(self, fallback_result=False):
605
self.fallback_result = fallback_result
606
self.error_count = 0
607
608
def evaluate_with_fallback(self, rule_text, data, context=None):
609
"""Evaluate rule with fallback on errors."""
610
try:
611
rule = rule_engine.Rule(rule_text, context=context)
612
return rule.matches(data)
613
except rule_engine.EngineError as e:
614
self.error_count += 1
615
logging.warning(f"Rule evaluation failed, using fallback: {e.message}")
616
return self.fallback_result
617
618
def get_error_rate(self, total_evaluations):
619
"""Calculate error rate for monitoring."""
620
return self.error_count / total_evaluations if total_evaluations > 0 else 0
621
```