Skip to content

Commit 6e1b394

Browse files
authored
Update docs regarding missing aggregation functions (#15)
1 parent 317edcd commit 6e1b394

File tree

5 files changed

+105
-21
lines changed

5 files changed

+105
-21
lines changed

docs/how-to-guides/cli-recipes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,8 @@ cel 'users.map(u, {
548548
"permissions_count": u.permissions.size()
549549
})' --context-file users.json
550550

551-
# Filter and aggregate
552-
cel 'products.filter(p, p.price > 100).map(p, p.price).fold(sum, 0, sum + item)' \
551+
# Filter and extract prices
552+
cel 'products.filter(p, p.price > 100).map(p, p.price)' \
553553
--context-file products.json
554554

555555
# Nested filtering

docs/reference/cel-compliance.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ This document tracks the compliance of this Python CEL implementation with the [
66

77
- **Implementation**: Based on [`cel`](https://crates.io/crates/cel) v0.11.0 Rust crate (formerly cel-interpreter)
88
- **Estimated Compliance**: ~80% of CEL specification features.
9-
- **Test Coverage**: 300+ tests across 15+ test files including comprehensive CLI testing and upstream improvement detection
9+
- **Test Coverage**: 300+ tests across 16+ test files including comprehensive CLI testing and upstream improvement detection
1010

1111
## 🚨 Missing Features & Severity Overview
1212

13-
| **Feature** | **Severity** | **Impact** | **Workaround Available** | **Upstream Priority** |
14-
|-------------|--------------|------------|--------------------------|----------------------|
15-
| **OR operator behavior** | 🔴 **HIGH** | Returns original values instead of booleans | Use explicit boolean conversion | **CRITICAL** |
16-
| **String utility functions** | 🟡 **MEDIUM** | Limited string processing capabilities | Use Python context functions | **HIGH** |
17-
| **Type introspection (`type()`)** | 🟡 **MEDIUM** | No runtime type checking | Use Python type checking | **HIGH** |
18-
| **Mixed int/uint arithmetic** | 🟡 **MEDIUM** | Manual type conversion needed | Use explicit casting | **MEDIUM** |
19-
| **Mixed-type arithmetic in macros** | 🟡 **MEDIUM** | Type coercion issues in collections | Ensure type consistency | **MEDIUM** |
20-
| **Bytes concatenation** | 🟢 **LOW** | Cannot concatenate byte arrays | Convert through string | **LOW** |
21-
| **Math functions (`ceil`, `floor`)** | 🟢 **LOW** | No mathematical utilities | Use Python context functions | **LOW** |
22-
| **Optional values** | 🟢 **LOW** | No optional chaining syntax | Use `has()` checks | **FUTURE** |
13+
| **Feature** | **Severity** | **Impact** | **Workaround Available** | **Upstream Priority** |
14+
|-----------------------------------------------------|--------------|------------|--------------------------|----------------------|
15+
| **OR operator behavior** | 🔴 **HIGH** | Returns original values instead of booleans | Use explicit boolean conversion | **CRITICAL** |
16+
| **String utility functions** | 🟡 **MEDIUM** | Limited string processing capabilities | Use Python context functions | **HIGH** |
17+
| **Type introspection (`type()`)** | 🟡 **MEDIUM** | No runtime type checking | Use Python type checking | **HIGH** |
18+
| **Mixed int/uint arithmetic** | 🟡 **MEDIUM** | Manual type conversion needed | Use explicit casting | **MEDIUM** |
19+
| **Mixed-type arithmetic in macros** | 🟡 **MEDIUM** | Type coercion issues in collections | Ensure type consistency | **MEDIUM** |
20+
| **Bytes concatenation** | 🟢 **LOW** | Cannot concatenate byte arrays | Convert through string | **LOW** |
21+
| **Math functions (`ceil`, `floor`)** | 🟢 **LOW** | No mathematical utilities | Use Python context functions | **LOW** |
22+
| **Collection aggregation (`sum`, `fold`, `reduce`)** | 🟢 **LOW** | No aggregation functions | Use Python context functions | **LOW** |
23+
| **Optional values** | 🟢 **LOW** | No optional chaining syntax | Use `has()` checks | **FUTURE** |
2324

2425
**Legend**: 🔴 High Impact | 🟡 Medium Impact | 🟢 Low Impact
2526

@@ -134,6 +135,7 @@ count + 1 // If count=5, stays as 5 + 1 → 6
134135
| `matches()` | `string.matches(pattern) -> bool` | Regex matching | `bool` | ✅ Working |
135136
| `min()` | `min(list) -> value` | Find minimum value | Various | ✅ Working |
136137
| `max()` | `max(list) -> value` | Find maximum value | Various | ✅ Working |
138+
| `sum()` | `sum(list) -> number` | Sum numeric values | N/A |**NOT AVAILABLE** |
137139

138140
### ✅ String Operations
139141
- **contains()**: `"hello".contains("ell")``True`
@@ -150,6 +152,10 @@ count + 1 // If count=5, stays as 5 + 1 → 6
150152
- **filter()**: `[1,2,3].filter(x, x > 1)``[2.0, 3.0]` (with type coercion)
151153
- **map()**: Limited due to type system restrictions ⚠️ **PARTIAL** (requires type-compatible operations)
152154

155+
### ❌ Missing Collection Functions
156+
- **fold()**: `[1,2,3].fold(0, sum, sum + x)` - Collection aggregation ❌ **NOT AVAILABLE**
157+
- **reduce()**: `reduce([1,2,3], 0, sum + x)` - Reduction operations ❌ **NOT AVAILABLE**
158+
153159
### ✅ Python Integration
154160
- **Automatic type conversion**: Seamless Python ↔ CEL type mapping
155161
- **Context variables**: Access Python objects in expressions
@@ -350,6 +356,7 @@ This section covers upstream work, detection strategies, and contribution opport
350356
- **Detection**: ✅ Full detection for all missing functions
351357
**Missing functions**:
352358
- Math: `ceil()`, `floor()`, `round()` - Mathematical functions
359+
- Collection: `fold()`, `reduce()` - Collection aggregation functions
353360
- Collection: Enhanced `in` operator behaviors
354361
- URL/IP: `isURL()`, `isIP()` - Validation functions (available in some CEL implementations)
355362

@@ -483,7 +490,7 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in
483490
### 🎯 Upstream Contribution Priorities
484491

485492
#### High Priority (Ready for Contribution)
486-
1. **String utility functions** - ✅ **Detection Ready**
493+
1. **String utility functions** - ✅ **Detection Ready** (`test_upstream_detection.py`)
487494
- Functions: `lowerAscii`, `upperAscii`, `indexOf`, `lastIndexOf`, `substring`, `replace`, `split`, `join`
488495
- Impact: **MEDIUM** - Widely used in string processing applications
489496
- Contribution path: cel crate standard library expansion
@@ -493,7 +500,7 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in
493500
- Impact: **HIGH** - Breaks specification conformance
494501
- Contribution path: Core logical operation fixes
495502

496-
3. **Type introspection function** - ✅ **Detection Ready**
503+
3. **Type introspection function** - ✅ **Detection Ready** (`test_upstream_detection.py`)
497504
- Function: `type()` for runtime type checking
498505
- Impact: **MEDIUM** - Useful for dynamic expressions
499506
- Contribution path: Leverage existing type system infrastructure
@@ -510,12 +517,17 @@ Both the CLI tool and the core `evaluate()` function now handle all malformed in
510517
- Contribution path: Arithmetic type coercion enhancements
511518

512519
#### Low Priority (Future Features)
513-
6. **Math functions** - ✅ **Detection Ready**
520+
6. **Collection aggregation functions** - ✅ **Detection Ready**
521+
- Functions: `sum()`, `fold()`, `reduce()`
522+
- Impact: **LOW** - Can be implemented via Python context
523+
- Contribution path: Standard library expansion
524+
525+
7. **Math functions** - ✅ **Detection Ready**
514526
- Functions: `ceil`, `floor`, `round`
515527
- Impact: **LOW** - Can be implemented via Python context
516528
- Contribution path: Standard library expansion
517529

518-
7. **Optional value handling** - ✅ **Detection Ready**
530+
8. **Optional value handling** - ✅ **Detection Ready**
519531
- Features: `optional.of()`, `.orValue()`, `?` chaining
520532
- Impact: **LOW** - Alternative patterns exist
521533
- Contribution path: Type system extensions

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,6 @@ fn cel(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
730730
pyo3_log::init();
731731

732732
m.add_function(wrap_pyfunction!(evaluate, m)?)?;
733-
734733
m.add_class::<context::Context>()?;
735734
Ok(())
736735
}

tests/test_functions.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@
66
import pytest
77

88

9+
class TestBuiltInCollectionFunctions:
10+
"""Test built-in collection functions that work in CEL."""
11+
12+
def test_min_function_works(self):
13+
"""Test that min() function works correctly."""
14+
assert cel.evaluate("min([3, 1, 4, 1, 5])") == 1
15+
assert cel.evaluate("min([1.5, 2.3, 0.8])") == 0.8
16+
assert cel.evaluate("min(['banana', 'apple', 'cherry'])") == "apple"
17+
18+
def test_max_function_works(self):
19+
"""Test that max() function works correctly."""
20+
assert cel.evaluate("max([3, 1, 4, 1, 5])") == 5
21+
assert cel.evaluate("max([1.5, 2.3, 0.8])") == 2.3
22+
assert cel.evaluate("max(['banana', 'apple', 'cherry'])") == "cherry"
23+
24+
925
def test_custom_function():
1026
def custom_function(a, b):
1127
return a + b
@@ -366,9 +382,9 @@ def simple_add(a, b):
366382
end_time = time.perf_counter()
367383
avg_time = (end_time - start_time) / iterations
368384

369-
# Should be reasonably fast (under 200 microseconds per call)
370-
# Adjusted threshold for realistic hardware performance
371-
assert avg_time < 0.0002, f"Function call too slow: {avg_time * 1000000:.1f} μs per call"
385+
# Should be reasonably fast (under 300 microseconds per call)
386+
# Adjusted threshold for realistic hardware performance and CI environments
387+
assert avg_time < 0.0003, f"Function call too slow: {avg_time * 1000000:.1f} μs per call"
372388

373389
def test_complex_function_call_performance(self):
374390
"""Test performance of more complex function calls."""

tests/test_upstream_improvements.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,62 @@ def test_join_not_implemented(self):
254254
cel.evaluate('["hello", "world"].join(",")')
255255

256256

257+
class TestMissingAggregationFunctions:
258+
"""Test aggregation functions that are missing from CEL."""
259+
260+
def test_sum_function_not_available(self):
261+
"""
262+
Test that sum() function is not currently available.
263+
264+
When this test starts failing, sum() has been implemented upstream.
265+
"""
266+
with pytest.raises(RuntimeError, match="Undefined variable or function.*sum"):
267+
cel.evaluate("sum([1, 2, 3, 4, 5])")
268+
269+
def test_fold_function_not_available(self):
270+
"""
271+
Test that fold() is not available - various syntax attempts.
272+
273+
When this test starts failing, fold() has been implemented upstream.
274+
"""
275+
# Method syntax
276+
with pytest.raises((RuntimeError, ValueError)):
277+
cel.evaluate("[1, 2, 3, 4, 5].fold(0, (acc, x) -> acc + x)")
278+
279+
# Global function syntax
280+
with pytest.raises(RuntimeError, match="Undefined variable or function.*fold"):
281+
cel.evaluate("fold([1, 2, 3], 0, sum + x)")
282+
283+
def test_reduce_function_not_available(self):
284+
"""
285+
Test that reduce() is not available - various syntax attempts.
286+
287+
When this test starts failing, reduce() has been implemented upstream.
288+
"""
289+
# Global function syntax
290+
with pytest.raises(RuntimeError, match="Undefined variable or function.*reduce"):
291+
cel.evaluate("reduce([1, 2, 3, 4, 5], 0, sum + x)")
292+
293+
# Method syntax
294+
with pytest.raises((RuntimeError, ValueError)):
295+
cel.evaluate("[1, 2, 3].reduce(0, (acc, x) -> acc + x)")
296+
297+
@pytest.mark.xfail(reason="Aggregation functions not implemented in cel v0.11.0", strict=False)
298+
def test_aggregation_functions_expected_behavior(self):
299+
"""
300+
Test expected aggregation function behavior when implemented.
301+
302+
This test will pass when upstream implements sum(), fold(), reduce().
303+
"""
304+
# Sum function
305+
assert cel.evaluate("sum([1, 2, 3, 4, 5])") == 15
306+
assert cel.evaluate("sum([1.1, 2.2, 3.3])") == pytest.approx(6.6)
307+
308+
# Fold/reduce functions (syntax may differ when actually implemented)
309+
assert cel.evaluate("[1, 2, 3, 4].fold(0, (acc, x) -> acc + x)") == 10
310+
assert cel.evaluate("[1, 2, 3].fold(1, (acc, x) -> acc * x)") == 6
311+
312+
257313
class TestMathFunctions:
258314
"""Test missing mathematical functions."""
259315

@@ -316,6 +372,7 @@ def test_upstream_improvements_summary():
316372
upstream improvements we're monitoring.
317373
"""
318374
improvements_to_watch = {
375+
"Missing aggregation functions": ["sum()", "fold()", "reduce()"],
319376
"String functions": [
320377
"lowerAscii",
321378
"upperAscii",

0 commit comments

Comments
 (0)