cel-rs-rb 0.2.0-aarch64-linux → 0.3.0-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: 47430d9c76013edfe991e5fc0aa6d4fa26bf33b0c2472a02b56c18e071783263
4
- data.tar.gz: 2133c9add306a5fd84f0f54c1b9e8e52b5dcae1f377f6d8a120babd009b4e910
3
+ metadata.gz: 445ac2d5635bf3c2666de9e84d34ba85be81b3eb0c03ccc4f2f13e7e790a6aff
4
+ data.tar.gz: d7d7d7813f3af99eca0863f8a3f7eb1f6e9f82f81047e8b1890b16e0fbc1ddf9
5
5
  SHA512:
6
- metadata.gz: 06da525ab6ab71104ec56b5f2d0c0956ee63a7e93471f9d772b3cb4c3c9cdf5fab91f1f0a2e1a7b7c852dd482a8508fbb15eaedc1e8ec364b67d5868e2db7380
7
- data.tar.gz: f2e056060ec5db8cb7cf17237c8d79982d8b3a69d6631458e5450316dc7204c12a74f4e16d6a4d5f8ba471b5091eef6827d031b423100df0bfd56294b212d0cf
6
+ metadata.gz: 738c2ac714197543c781b8b2730c637277a7107ee075b3c38255a0dbfc9c49fc32073744c8b98cd9b4567d17c3055190773a384e0cf8abe8da1b6c6960ec7d41
7
+ data.tar.gz: 56afd495347ad882026c89926abb66cddc5b514a807f02c1469a3a7692f7b577ce944f548cd87b396b98d161c1969762881a70d5e47e900cd35c3820cf030468
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,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** for six platforms
118
- (`x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`, `aarch64-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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CEL
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
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
@@ -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}.contains('a')" => true,
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.2.0
4
+ version: 0.3.0
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-10 00:00:00.000000000 Z
11
+ date: 2026-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake