cel-rs-rb 0.2.0-x86_64-linux → 0.3.0-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +75 -7
- data/lib/cel/3.3/cel.so +0 -0
- data/lib/cel/3.4/cel.so +0 -0
- data/lib/cel/4.0/cel.so +0 -0
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +12 -0
- data/spec/cel_spec.rb +109 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6141fff7b7af7e907394dd3c8166180b2709c853a4cdb8074fc5d3236e243147
|
|
4
|
+
data.tar.gz: 007f3dd5efd09f4f6537f5c4c3f7619fa055fef65386b7d7040793e6bdc3f2eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4e4808206e95e90e5ffb0cce51d7a677affde137043c2d4b051d9c3ef55c8e748c41381eba6eed1d4d45eaabb6b30ebfad3e516ece40f24eab692df9bf3a2c9
|
|
7
|
+
data.tar.gz: 0beaa7107f2c89aa24b496420143cbeebee3feeaa921876042acff3e5aa01dc1eed7e5bb9e2ce8d46b045a60a0da585ff9f48c1e26e668a752e31baee1309e6d
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/cel-rs-rb)
|
|
4
4
|
|
|
5
|
-
Ruby bindings for the Rust [`cel`](https://crates.io/crates/cel) crate, implemented with [Magnus](https://github.com/matsadler/magnus).
|
|
5
|
+
Ruby bindings for the Rust [`cel`](https://crates.io/crates/cel) 0.15 crate, implemented with [Magnus](https://github.com/matsadler/magnus).
|
|
6
6
|
|
|
7
7
|
## Goals
|
|
8
8
|
|
|
@@ -32,10 +32,69 @@ program.execute(context)
|
|
|
32
32
|
# => 60
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Optional values
|
|
36
|
+
|
|
37
|
+
Use optional access to handle missing fields, and `orValue` to supply a default:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
context = CEL::Context.build(user: {})
|
|
41
|
+
CEL.compile("user.?name.orValue('unknown')").execute(context)
|
|
42
|
+
# => "unknown"
|
|
43
|
+
|
|
44
|
+
context = CEL::Context.build(user: {"name" => "Ada"})
|
|
45
|
+
CEL.compile("user.?name.optMap(n, n.size()).orValue(0)").execute(context)
|
|
46
|
+
# => 3
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Unwrap optional values inside CEL with `orValue` or `value()` before returning
|
|
50
|
+
them to Ruby. Returning an optional directly raises `CEL::TypeError`; it is not
|
|
51
|
+
automatically converted to its contained value or `nil`.
|
|
52
|
+
|
|
53
|
+
### `max` and `min`
|
|
54
|
+
|
|
55
|
+
Default contexts include `max` and `min`. They take a list or positional arguments and
|
|
56
|
+
return `nil` when given no values:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
CEL.compile("max(2, 9, -3)").execute # => 9
|
|
60
|
+
CEL.compile("min([2, 9, -3])").execute # => -3
|
|
61
|
+
CEL.compile("max([])").execute # => nil
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Extension libraries
|
|
65
|
+
|
|
66
|
+
Opt into upstream CEL extension libraries when compiling a program:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
program = CEL.compile(
|
|
70
|
+
"[label.lowerAscii(), scores.sort(), math.greatest(scores), base64.encode(b'hi')]",
|
|
71
|
+
extensions: [:strings, :lists, :math, :encoders]
|
|
72
|
+
)
|
|
73
|
+
program.execute(CEL::Context.build(label: "TacoCat", scores: [2, 9, -3]))
|
|
74
|
+
# => ["tacocat", [-3, 2, 9], 9, "aGk="]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Library | Examples |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| [`strings`](https://docs.rs/cel/0.15.0/cel/extensions/fn.strings.html) | `'TacoCat'.lowerAscii()`, `'a,b'.split(',')` |
|
|
80
|
+
| [`lists`](https://docs.rs/cel/0.15.0/cel/extensions/fn.lists.html) | `lists.range(3)`, `[3, 1, 2].sort()`, `items.sortBy(x, x.score)` |
|
|
81
|
+
| [`math`](https://docs.rs/cel/0.15.0/cel/extensions/fn.math.html) | `math.greatest(1, 5, 3)`, `math.least([1, 5, 3])` |
|
|
82
|
+
| [`encoders`](https://docs.rs/cel/0.15.0/cel/extensions/fn.encoders.html) | `base64.encode(b'hi')`, `base64.decode('aGk=')` |
|
|
83
|
+
|
|
84
|
+
`CEL::Program.compile` accepts the same `extensions:` option. Names can be
|
|
85
|
+
symbols or strings; unknown or repeated names raise `ArgumentError`.
|
|
86
|
+
|
|
87
|
+
Extensions are disabled by default and registered per program, for both macro
|
|
88
|
+
expansion and execution. They work with bare `execute` or a default context,
|
|
89
|
+
including its variables and Ruby callbacks. They do not change other programs
|
|
90
|
+
or add functions to explicitly empty contexts. Ruby callbacks that conflict
|
|
91
|
+
with enabled extension functions raise `CEL::ExecutionError` during execution,
|
|
92
|
+
just like conflicts with standard CEL builtins.
|
|
93
|
+
|
|
35
94
|
### API mapping
|
|
36
95
|
|
|
37
|
-
- `CEL.compile(source)` -> `CEL::Program`
|
|
38
|
-
- `CEL::Program.compile(source)` -> `CEL::Program`
|
|
96
|
+
- `CEL.compile(source, extensions: [])` -> `CEL::Program`
|
|
97
|
+
- `CEL::Program.compile(source, extensions: [])` -> `CEL::Program`
|
|
39
98
|
- `CEL::Program#execute(context = nil)` -> Ruby value
|
|
40
99
|
- `CEL::Program#references` -> `{ "variables" => [...], "functions" => [...] }`
|
|
41
100
|
- `CEL::Context.new(empty = false)` -> context with builtins (`false`) or empty (`true`)
|
|
@@ -114,10 +173,12 @@ CI also runs the test suite on Ruby 3.3, Ruby 3.4, and Ruby 4.0 through Buildkit
|
|
|
114
173
|
|
|
115
174
|
## Releasing
|
|
116
175
|
|
|
117
|
-
Releases publish **precompiled native gems**
|
|
118
|
-
(`x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`,
|
|
119
|
-
`arm64-darwin`, `x86_64-darwin`) to
|
|
120
|
-
[rubygems.org](https://rubygems.org/gems/cel-rs-rb).
|
|
176
|
+
Releases publish a generic `ruby` platform gem and **precompiled native gems**
|
|
177
|
+
for six platforms (`x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`,
|
|
178
|
+
`aarch64-linux-musl`, `arm64-darwin`, `x86_64-darwin`) to
|
|
179
|
+
[rubygems.org](https://rubygems.org/gems/cel-rs-rb). The generic gem keeps
|
|
180
|
+
RubyGems' latest-version metadata aligned; supported platforms should still
|
|
181
|
+
resolve to their matching precompiled native gem.
|
|
121
182
|
|
|
122
183
|
### Cutting a release
|
|
123
184
|
|
|
@@ -148,5 +209,12 @@ The `release:verify-tag` step fails the build if the tag and
|
|
|
148
209
|
|
|
149
210
|
## Compatibility notes
|
|
150
211
|
|
|
212
|
+
- Compilation uses the standard CEL environment, including optional access syntax (`user.?name`) and the `optMap` / `optFlatMap` macros.
|
|
213
|
+
- The upstream strings, lists, math, and encoders extension libraries are opt-in through the `extensions:` compile option.
|
|
214
|
+
- cel 0.15 no longer includes `max` and `min` by default, so default contexts register them for compatibility with cel 0.13. See [`max` and `min`](#max-and-min).
|
|
215
|
+
- When upgrading from cel 0.13, replace list/map `.contains(value)` expressions with `value in collection`; `.contains()` now only accepts strings. Map literals with duplicate keys raise `CEL::ExecutionError`, and string `size()` counts Unicode code points rather than bytes. See the [cel 0.15 release notes](https://github.com/cel-rust/cel-rust/releases/tag/v0.15.0) for further CEL semantic changes.
|
|
216
|
+
- `string(bytes)` now rejects invalid UTF-8. Numeric conversions reject out-of-range results, and parsed timestamps and durations must fit CEL's allowed ranges.
|
|
217
|
+
- `all()` and `exists()` can now absorb an earlier evaluation error when a later element determines the result. For example, `[2, 1].exists(e, 6 / (2 - e) == 6)` returns `true` despite the first element's division by zero.
|
|
218
|
+
- Ruby callbacks cannot shadow standard CEL builtins (such as `size`) in a default context; the compatibility functions `max` and `min` are exceptions. Conflicts raise `CEL::ExecutionError` when `Program#execute(context)` builds the execution snapshot, even if the expression does not call the conflicting function. Use `CEL::Context.new(true)` or `CEL::Context.build(empty: true)` to define functions without builtins. Registering the same custom callback name again replaces the previous callback.
|
|
151
219
|
- Some CEL value variants (e.g. opaque custom Rust types) cannot be fully marshaled to Ruby and raise `CEL::TypeError`.
|
|
152
220
|
- This project targets broad CEL compatibility, but parity for every Rust-only extension point is still evolving.
|
data/lib/cel/3.3/cel.so
CHANGED
|
Binary file
|
data/lib/cel/3.4/cel.so
CHANGED
|
Binary file
|
data/lib/cel/4.0/cel.so
CHANGED
|
Binary file
|
data/lib/cel/version.rb
CHANGED
data/lib/cel.rb
CHANGED
|
@@ -10,6 +10,10 @@ rescue LoadError
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
module CEL
|
|
13
|
+
def self.compile(source, extensions: [])
|
|
14
|
+
Program.compile(source, extensions: extensions)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
class Context
|
|
14
18
|
class << self
|
|
15
19
|
alias_method :__native_new, :new
|
|
@@ -34,6 +38,14 @@ module CEL
|
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
class Program
|
|
41
|
+
class << self
|
|
42
|
+
alias_method :__native_compile, :compile
|
|
43
|
+
|
|
44
|
+
def compile(source, extensions: [])
|
|
45
|
+
__native_compile(source, extensions.map(&:to_s))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
37
49
|
alias_method :__native_execute, :execute
|
|
38
50
|
|
|
39
51
|
def execute(context = nil)
|
data/spec/cel_spec.rb
CHANGED
|
@@ -13,6 +13,49 @@ RSpec.describe CEL do
|
|
|
13
13
|
it "raises parse errors for invalid programs" do
|
|
14
14
|
expect { CEL.compile("1 +") }.to raise_error(CEL::ParseError)
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
it "enables each upstream extension for compilation and execution" do
|
|
18
|
+
tests = {
|
|
19
|
+
strings: ["'TacoCat'.lowerAscii()", "tacocat"],
|
|
20
|
+
lists: ["[{'n': 8}, {'n': 2}, {'n': 5}].sortBy(x, x.n).map(x, x.n)", [2, 5, 8]],
|
|
21
|
+
math: ["math.greatest(2, 9, -3)", 9],
|
|
22
|
+
encoders: ["base64.encode(b'hi')", "aGk="]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
tests.each do |extension, (expression, expected)|
|
|
26
|
+
program = CEL.compile(expression, extensions: [extension])
|
|
27
|
+
expect(program.execute).to eq(expected)
|
|
28
|
+
expect(program.execute(CEL::Context.new)).to eq(expected)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "combines extensions with context variables and Ruby callbacks" do
|
|
33
|
+
context = CEL::Context.build(label: "TacoCat", scores: [2, 9, -3]) do |ctx|
|
|
34
|
+
ctx.define_function("decorate") { |value| "[#{value}]" }
|
|
35
|
+
end
|
|
36
|
+
program = CEL::Program.compile(
|
|
37
|
+
"[decorate(label.lowerAscii()), scores.sort(), math.greatest(scores), base64.encode(b'hi')]",
|
|
38
|
+
extensions: ["strings", "lists", "math", "encoders"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect(program.execute(context)).to eq(["[tacocat]", [-3, 2, 9], 9, "aGk="])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "keeps extensions local to a program and excludes them from empty contexts" do
|
|
45
|
+
context = CEL::Context.new
|
|
46
|
+
enabled = CEL.compile("'ABC'.lowerAscii()", extensions: [:strings])
|
|
47
|
+
expect(enabled.execute(context)).to eq("abc")
|
|
48
|
+
expect { CEL.compile("'ABC'.lowerAscii()").execute(context) }.to raise_error(CEL::ExecutionError)
|
|
49
|
+
expect { enabled.execute(CEL::Context.new(true)) }.to raise_error(CEL::ExecutionError)
|
|
50
|
+
|
|
51
|
+
context.define_function("lowerAscii") { |value| value }
|
|
52
|
+
expect { enabled.execute(context) }.to raise_error(CEL::ExecutionError, /lowerAscii/)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "rejects unknown and repeated extension registrations" do
|
|
56
|
+
expect { CEL.compile("1", extensions: [:unknown]) }.to raise_error(ArgumentError, /unknown/)
|
|
57
|
+
expect { CEL.compile("1", extensions: [:strings, "strings"]) }.to raise_error(ArgumentError)
|
|
58
|
+
end
|
|
16
59
|
end
|
|
17
60
|
|
|
18
61
|
describe CEL::Context do
|
|
@@ -63,6 +106,31 @@ RSpec.describe CEL do
|
|
|
63
106
|
expect(CEL.compile("'hello'.startsWith('he')").execute(context)).to eq(true)
|
|
64
107
|
end
|
|
65
108
|
|
|
109
|
+
it "marshals callback receivers, arguments, and results" do
|
|
110
|
+
context = CEL::Context.build(prefix: {"label" => "result"})
|
|
111
|
+
context.define_function("combine") do |target, values, suffix|
|
|
112
|
+
{target["label"] => values + [suffix]}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
expect(CEL.compile("prefix.combine([2, 5], 9)").execute(context)).to eq("result" => [2, 5, 9])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "reports builtin callback conflicts when building the execution context" do
|
|
119
|
+
context = CEL::Context.new
|
|
120
|
+
context.define_function("size") { |_| 99 }
|
|
121
|
+
|
|
122
|
+
expect { CEL.compile("size([1, 2])").execute(context) }
|
|
123
|
+
.to raise_error(CEL::ExecutionError, /size/)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "replaces callbacks with the same name" do
|
|
127
|
+
context = CEL::Context.new
|
|
128
|
+
context.define_function("adjust") { |value| value + 1 }
|
|
129
|
+
context.define_function("adjust") { |value| value + 7 }
|
|
130
|
+
|
|
131
|
+
expect(CEL.compile("adjust(3)").execute(context)).to eq(10)
|
|
132
|
+
end
|
|
133
|
+
|
|
66
134
|
it "raises clear type error for unsupported variable types" do
|
|
67
135
|
context = CEL::Context.new
|
|
68
136
|
expect { context.add_variable("bad", Object.new) }.to raise_error(CEL::TypeError)
|
|
@@ -77,6 +145,36 @@ RSpec.describe CEL do
|
|
|
77
145
|
expect(refs["functions"]).to include("size")
|
|
78
146
|
end
|
|
79
147
|
|
|
148
|
+
it "registers max and min by default" do
|
|
149
|
+
tests = {
|
|
150
|
+
"max(2, 9, -3)" => 9,
|
|
151
|
+
"min(2, 9, -3)" => -3,
|
|
152
|
+
"max([2, 9, -3])" => 9,
|
|
153
|
+
"min([2, 9, -3])" => -3,
|
|
154
|
+
"max()" => nil,
|
|
155
|
+
"min([])" => nil
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
[nil, CEL::Context.new].each do |context|
|
|
159
|
+
tests.each do |expression, expected|
|
|
160
|
+
expect(CEL.compile(expression).execute(context)).to eq(expected)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "leaves max and min out of empty contexts and allows custom callbacks" do
|
|
166
|
+
["max", "min"].each do |name|
|
|
167
|
+
program = CEL.compile("#{name}(2, 9)")
|
|
168
|
+
empty = CEL::Context.new(true)
|
|
169
|
+
expect { program.execute(empty) }.to raise_error(CEL::ExecutionError)
|
|
170
|
+
|
|
171
|
+
[empty, CEL::Context.new].each do |context|
|
|
172
|
+
context.define_function(name) { |a, b| a + b }
|
|
173
|
+
expect(program.execute(context)).to eq(11)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
80
178
|
it "ports core CEL suite behavior examples" do
|
|
81
179
|
tests = {
|
|
82
180
|
"size([1,2,3]) == 3" => true,
|
|
@@ -84,8 +182,13 @@ RSpec.describe CEL do
|
|
|
84
182
|
"[1,2,3].filter(x, x > 1)" => [2, 3],
|
|
85
183
|
"[1,2,3].exists(x, x == 2)" => true,
|
|
86
184
|
"[1,2,3].all(x, x > 0)" => true,
|
|
87
|
-
"{'a': 1}
|
|
88
|
-
"optional.of(1).hasValue()" => true
|
|
185
|
+
"'a' in {'a': 1}" => true,
|
|
186
|
+
"optional.of(1).hasValue()" => true,
|
|
187
|
+
"size('café😊')" => 5,
|
|
188
|
+
"{'name': 'Ada'}.?name.optMap(n, n.size()).orValue(0)" => 3,
|
|
189
|
+
"{}.?name.optMap(n, n.size()).orValue(0)" => 0,
|
|
190
|
+
"{'user': {'name': 'Ada'}}.?user.optFlatMap(u, u.?name).orValue('missing')" => "Ada",
|
|
191
|
+
"[2, 1].exists(e, 6 / (2 - e) == 6)" => true
|
|
89
192
|
}
|
|
90
193
|
|
|
91
194
|
tests.each do |expr, expected|
|
|
@@ -113,6 +216,10 @@ RSpec.describe CEL do
|
|
|
113
216
|
expect { program.execute }.to raise_error(CEL::ExecutionError)
|
|
114
217
|
end
|
|
115
218
|
|
|
219
|
+
it "rejects duplicate map literal keys" do
|
|
220
|
+
expect { CEL.compile("{'a': 1, 'a': 2}").execute }.to raise_error(CEL::ExecutionError)
|
|
221
|
+
end
|
|
222
|
+
|
|
116
223
|
it "releases GVL so other ruby threads can progress" do
|
|
117
224
|
context = CEL::Context.new
|
|
118
225
|
context.add_variable("items", Array.new(15_000, 1))
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cel-rs-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- CEL Ruby Contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|