cel-rs-rb 0.2.0.pre.1-aarch64-linux → 0.3.1-aarch64-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0131ec68b9a0fc2590167779c08ee336f7d4a54c9b789d004a1b57938ab5657
4
- data.tar.gz: 289fdf028556b83a4f4a05b87ca0f61367ceaaaeef1d779da848448416699c3a
3
+ metadata.gz: cdb06e3dbfb08cf5f35c8c57c0a3f157dcfb3dfdecabc242bb717e98d5faac67
4
+ data.tar.gz: 4fcbdb696df2b5b6a2f123c3611370206e75d8a5d60337c10d4586e8c2b8a532
5
5
  SHA512:
6
- metadata.gz: 345c10b1f3327de41e00317c3ded1aef5e0c4fcc39befbfef05c3212ea75d0b649f7c56bb8d56574cbad9ed3edc1a6a26c86988cc4e4815dd7d34568aa9735f2
7
- data.tar.gz: 7dbe2387e69626c7f58e2b8f3eff9f72134b38d48df083ffa3cebe64211382469923995aed748ef2da5350fc0c7540c32d369fec0b5c2daf6025a2610e132ab7
6
+ metadata.gz: f6400aa89ca7995314ac5c13ce38e7e30bc01be90bf55a2391584cc143d8b93aef03da5651bd5cc6a34e1ab4da0c7b468326695a94c09a39713d808da292cc60
7
+ data.tar.gz: 163037c4a8787f598a774dad13ed44d075b9d77b2b9308832a04794c229945161712e75bbe961a7bb1ddde37a20184f93647ba8e7838b777416285bbc247fdca
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1
4
+
5
+ ### Fixed
6
+
7
+ - Keep registered Ruby callback Procs and their captured values alive while their
8
+ `CEL::Context` is reachable. Prevents use-after-free after the caller drops its
9
+ own reference and Ruby runs garbage collection. Callbacks are also pinned during
10
+ compaction, including when collection runs inside a callback.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/cel-rs-rb.svg)](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,9 +173,14 @@ 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** for five platforms
118
- (`x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`, `arm64-darwin`,
119
- `x86_64-darwin`) to [rubygems.org](https://rubygems.org/gems/cel-rs-rb).
176
+ See [CHANGELOG.md](CHANGELOG.md) for release notes. Update it alongside each version bump.
177
+
178
+ Releases publish a generic `ruby` platform gem and **precompiled native gems**
179
+ for six platforms (`x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`,
180
+ `aarch64-linux-musl`, `arm64-darwin`, `x86_64-darwin`) to
181
+ [rubygems.org](https://rubygems.org/gems/cel-rs-rb). The generic gem keeps
182
+ RubyGems' latest-version metadata aligned; supported platforms should still
183
+ resolve to their matching precompiled native gem.
120
184
 
121
185
  ### Cutting a release
122
186
 
@@ -125,20 +189,34 @@ file and tag the same version:
125
189
 
126
190
  ```bash
127
191
  # 1. Bump CEL::VERSION in lib/cel/version.rb (e.g. "0.2.0")
128
- # 2. Commit the bump
129
- git commit -am "Release v0.2.0"
192
+ # 2. Refresh Gemfile.lock's local gem version
193
+ bundle install
130
194
 
131
- # 3. Tag that commit (the v-prefix triggers the release pipeline)
195
+ # 3. Commit the bump
196
+ git add lib/cel/version.rb Gemfile.lock
197
+ git commit -m "Release v0.2.0"
198
+
199
+ # 4. Tag that commit (the v-prefix triggers the release pipeline)
132
200
  git tag v0.2.0
133
201
  git push origin main
134
202
  git push origin v0.2.0
135
203
  ```
136
204
 
205
+ `Gemfile.lock` includes a local `cel-rs-rb` path entry, so commit its version
206
+ change alongside `lib/cel/version.rb`.
207
+
137
208
  The `release:verify-tag` step fails the build if the tag and
138
209
  `CEL::VERSION` disagree, so the tag (`v0.2.0`) must match `version.rb`
139
210
  (`0.2.0`).
140
211
 
141
212
  ## Compatibility notes
142
213
 
214
+ - Compilation uses the standard CEL environment, including optional access syntax (`user.?name`) and the `optMap` / `optFlatMap` macros.
215
+ - The upstream strings, lists, math, and encoders extension libraries are opt-in through the `extensions:` compile option.
216
+ - 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).
217
+ - 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.
218
+ - `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.
219
+ - `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.
220
+ - 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.
143
221
  - Some CEL value variants (e.g. opaque custom Rust types) cannot be fully marshaled to Ruby and raise `CEL::TypeError`.
144
222
  - 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CEL
4
- VERSION = "0.2.0.pre.1"
4
+ VERSION = "0.3.1"
5
5
  end
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
  require "time"
5
+ require "open3"
5
6
 
6
7
  RSpec.describe CEL do
7
8
  describe ".compile" do
@@ -13,6 +14,49 @@ RSpec.describe CEL do
13
14
  it "raises parse errors for invalid programs" do
14
15
  expect { CEL.compile("1 +") }.to raise_error(CEL::ParseError)
15
16
  end
17
+
18
+ it "enables each upstream extension for compilation and execution" do
19
+ tests = {
20
+ strings: ["'TacoCat'.lowerAscii()", "tacocat"],
21
+ lists: ["[{'n': 8}, {'n': 2}, {'n': 5}].sortBy(x, x.n).map(x, x.n)", [2, 5, 8]],
22
+ math: ["math.greatest(2, 9, -3)", 9],
23
+ encoders: ["base64.encode(b'hi')", "aGk="]
24
+ }
25
+
26
+ tests.each do |extension, (expression, expected)|
27
+ program = CEL.compile(expression, extensions: [extension])
28
+ expect(program.execute).to eq(expected)
29
+ expect(program.execute(CEL::Context.new)).to eq(expected)
30
+ end
31
+ end
32
+
33
+ it "combines extensions with context variables and Ruby callbacks" do
34
+ context = CEL::Context.build(label: "TacoCat", scores: [2, 9, -3]) do |ctx|
35
+ ctx.define_function("decorate") { |value| "[#{value}]" }
36
+ end
37
+ program = CEL::Program.compile(
38
+ "[decorate(label.lowerAscii()), scores.sort(), math.greatest(scores), base64.encode(b'hi')]",
39
+ extensions: ["strings", "lists", "math", "encoders"]
40
+ )
41
+
42
+ expect(program.execute(context)).to eq(["[tacocat]", [-3, 2, 9], 9, "aGk="])
43
+ end
44
+
45
+ it "keeps extensions local to a program and excludes them from empty contexts" do
46
+ context = CEL::Context.new
47
+ enabled = CEL.compile("'ABC'.lowerAscii()", extensions: [:strings])
48
+ expect(enabled.execute(context)).to eq("abc")
49
+ expect { CEL.compile("'ABC'.lowerAscii()").execute(context) }.to raise_error(CEL::ExecutionError)
50
+ expect { enabled.execute(CEL::Context.new(true)) }.to raise_error(CEL::ExecutionError)
51
+
52
+ context.define_function("lowerAscii") { |value| value }
53
+ expect { enabled.execute(context) }.to raise_error(CEL::ExecutionError, /lowerAscii/)
54
+ end
55
+
56
+ it "rejects unknown and repeated extension registrations" do
57
+ expect { CEL.compile("1", extensions: [:unknown]) }.to raise_error(ArgumentError, /unknown/)
58
+ expect { CEL.compile("1", extensions: [:strings, "strings"]) }.to raise_error(ArgumentError)
59
+ end
16
60
  end
17
61
 
18
62
  describe CEL::Context do
@@ -56,6 +100,40 @@ RSpec.describe CEL do
56
100
  expect(CEL.compile("sum(1, 2, 3)").execute(context)).to eq(6)
57
101
  end
58
102
 
103
+ it "keeps registered callbacks alive through garbage collection and compaction" do
104
+ # Isolate native GC failures and discard the registering thread's stack roots.
105
+ output, status = Open3.capture2e(
106
+ RbConfig.ruby, "-I", File.expand_path("../lib", __dir__), "-rcel", "-rweakref", "-e", <<~RUBY
107
+ context, callbacks = Thread.new do
108
+ ctx = CEL::Context.new
109
+ prefix = +"retained:token:"
110
+ decorate = proc { |value| prefix + value }
111
+ collect = proc do
112
+ GC.start
113
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
114
+ 17
115
+ end
116
+ ctx.define_function("decorate", &decorate)
117
+ ctx.define_function("collect", &collect)
118
+ [ctx, [WeakRef.new(decorate), WeakRef.new(collect)]]
119
+ end.value
120
+
121
+ program = CEL.compile("[decorate('ok'), collect(), decorate('again')]")
122
+ 3.times do
123
+ GC.start
124
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
125
+ raise "registered callback was collected" unless callbacks.all?(&:weakref_alive?)
126
+ result = program.execute(context)
127
+ raise "incorrect callback result: \#{result.inspect}" unless result == [
128
+ "retained:token:ok", 17, "retained:token:again"
129
+ ]
130
+ end
131
+ RUBY
132
+ )
133
+
134
+ expect(status.success?).to eq(true), output
135
+ end
136
+
59
137
  it "passes method receiver as first block arg for method calls" do
60
138
  context = CEL::Context.new(true)
61
139
  context.define_function("startsWith") { |target, prefix| target.start_with?(prefix) }
@@ -63,6 +141,31 @@ RSpec.describe CEL do
63
141
  expect(CEL.compile("'hello'.startsWith('he')").execute(context)).to eq(true)
64
142
  end
65
143
 
144
+ it "marshals callback receivers, arguments, and results" do
145
+ context = CEL::Context.build(prefix: {"label" => "result"})
146
+ context.define_function("combine") do |target, values, suffix|
147
+ {target["label"] => values + [suffix]}
148
+ end
149
+
150
+ expect(CEL.compile("prefix.combine([2, 5], 9)").execute(context)).to eq("result" => [2, 5, 9])
151
+ end
152
+
153
+ it "reports builtin callback conflicts when building the execution context" do
154
+ context = CEL::Context.new
155
+ context.define_function("size") { |_| 99 }
156
+
157
+ expect { CEL.compile("size([1, 2])").execute(context) }
158
+ .to raise_error(CEL::ExecutionError, /size/)
159
+ end
160
+
161
+ it "replaces callbacks with the same name" do
162
+ context = CEL::Context.new
163
+ context.define_function("adjust") { |value| value + 1 }
164
+ context.define_function("adjust") { |value| value + 7 }
165
+
166
+ expect(CEL.compile("adjust(3)").execute(context)).to eq(10)
167
+ end
168
+
66
169
  it "raises clear type error for unsupported variable types" do
67
170
  context = CEL::Context.new
68
171
  expect { context.add_variable("bad", Object.new) }.to raise_error(CEL::TypeError)
@@ -77,6 +180,36 @@ RSpec.describe CEL do
77
180
  expect(refs["functions"]).to include("size")
78
181
  end
79
182
 
183
+ it "registers max and min by default" do
184
+ tests = {
185
+ "max(2, 9, -3)" => 9,
186
+ "min(2, 9, -3)" => -3,
187
+ "max([2, 9, -3])" => 9,
188
+ "min([2, 9, -3])" => -3,
189
+ "max()" => nil,
190
+ "min([])" => nil
191
+ }
192
+
193
+ [nil, CEL::Context.new].each do |context|
194
+ tests.each do |expression, expected|
195
+ expect(CEL.compile(expression).execute(context)).to eq(expected)
196
+ end
197
+ end
198
+ end
199
+
200
+ it "leaves max and min out of empty contexts and allows custom callbacks" do
201
+ ["max", "min"].each do |name|
202
+ program = CEL.compile("#{name}(2, 9)")
203
+ empty = CEL::Context.new(true)
204
+ expect { program.execute(empty) }.to raise_error(CEL::ExecutionError)
205
+
206
+ [empty, CEL::Context.new].each do |context|
207
+ context.define_function(name) { |a, b| a + b }
208
+ expect(program.execute(context)).to eq(11)
209
+ end
210
+ end
211
+ end
212
+
80
213
  it "ports core CEL suite behavior examples" do
81
214
  tests = {
82
215
  "size([1,2,3]) == 3" => true,
@@ -84,8 +217,13 @@ RSpec.describe CEL do
84
217
  "[1,2,3].filter(x, x > 1)" => [2, 3],
85
218
  "[1,2,3].exists(x, x == 2)" => true,
86
219
  "[1,2,3].all(x, x > 0)" => true,
87
- "{'a': 1}.contains('a')" => true,
88
- "optional.of(1).hasValue()" => true
220
+ "'a' in {'a': 1}" => true,
221
+ "optional.of(1).hasValue()" => true,
222
+ "size('café😊')" => 5,
223
+ "{'name': 'Ada'}.?name.optMap(n, n.size()).orValue(0)" => 3,
224
+ "{}.?name.optMap(n, n.size()).orValue(0)" => 0,
225
+ "{'user': {'name': 'Ada'}}.?user.optFlatMap(u, u.?name).orValue('missing')" => "Ada",
226
+ "[2, 1].exists(e, 6 / (2 - e) == 6)" => true
89
227
  }
90
228
 
91
229
  tests.each do |expr, expected|
@@ -113,6 +251,10 @@ RSpec.describe CEL do
113
251
  expect { program.execute }.to raise_error(CEL::ExecutionError)
114
252
  end
115
253
 
254
+ it "rejects duplicate map literal keys" do
255
+ expect { CEL.compile("{'a': 1, 'a': 2}").execute }.to raise_error(CEL::ExecutionError)
256
+ end
257
+
116
258
  it "releases GVL so other ruby threads can progress" do
117
259
  context = CEL::Context.new
118
260
  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.2.0.pre.1
4
+ version: 0.3.1
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - CEL Ruby Contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-01 00:00:00.000000000 Z
11
+ date: 2026-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -86,6 +86,7 @@ executables: []
86
86
  extensions: []
87
87
  extra_rdoc_files: []
88
88
  files:
89
+ - CHANGELOG.md
89
90
  - LICENSE
90
91
  - README.md
91
92
  - lib/cel.rb