cel 0.3.0 → 0.4.0
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/CHANGELOG.md +25 -0
- data/README.md +41 -9
- data/lib/cel/ast/elements/protobuf.rb +29 -26
- data/lib/cel/ast/elements.rb +52 -20
- data/lib/cel/ast/types.rb +2 -2
- data/lib/cel/checker.rb +47 -31
- data/lib/cel/context.rb +18 -19
- data/lib/cel/environment.rb +22 -4
- data/lib/cel/errors.rb +12 -0
- data/lib/cel/extensions/bind.rb +35 -0
- data/lib/cel/extensions/encoders.rb +41 -0
- data/lib/cel/extensions/math.rb +211 -0
- data/lib/cel/extensions/string.rb +228 -0
- data/lib/cel/extensions.rb +15 -0
- data/lib/cel/macro.rb +4 -3
- data/lib/cel/parser.rb +207 -23
- data/lib/cel/program.rb +32 -17
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +2 -1
- metadata +23 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12cebd55174d05db4f154e185872921949f3931817503fccb5dec9f516c3128d
|
|
4
|
+
data.tar.gz: 8fa9c386f343ecfdda97ce99c37296e15378763ab0308270fdc0e789cb4c9ab0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a46166c14750ef4fc245d1aca362f103d4340ddce4155c51e2f49e03ea64c5d812dccaf61ac073f9f6207995a2f8090a748fe06bf984159542385416a2e9e83a
|
|
7
|
+
data.tar.gz: cfa9d65dd070389fca1ed80817a83baee1efe193cab6830bfd0a3278051a8593a673a4de8e19c19db1a450312cad69ccb91ac49e5db2ee02d18d0efb301319c1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2025-11-18
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
`cel-ruby` supports the defined set of [CEL extensions](https://github.com/google/cel-go/blob/master/ext/README.md): math, string, bind and encoders.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
Cel::Environment.new.evaluate("math.bitShiftLeft(1, 2) ") #=> returns 4
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
|
|
15
|
+
When the `tzinfo-data` gem is loaded, expressions will support human-friendly timezones, such as "US/Central", for timestamps.
|
|
16
|
+
|
|
17
|
+
## [0.3.1] - 2025-08-01
|
|
18
|
+
|
|
19
|
+
### Improvements
|
|
20
|
+
|
|
21
|
+
* Support for depth protection, using the defaults from the spec (ex: recursion depth of 32, nesting depth of 32), and supporting API to override them (ex: `Cel::Environment.new(max_recursion_depth: 42))`).
|
|
22
|
+
* lazy-evaluating bindings to avoid upfront costly transformations of ruby objects into cel objects which are ultimately not used in expressions.
|
|
23
|
+
|
|
24
|
+
### Bugfixes
|
|
25
|
+
|
|
26
|
+
* `has()` macro correctly works for mapsl
|
|
27
|
+
|
|
3
28
|
## [0.3.0] - 2025-06-12
|
|
4
29
|
|
|
5
30
|
### Features
|
data/README.md
CHANGED
|
@@ -32,7 +32,7 @@ The usage pattern follows the pattern defined by [cel-go](https://github.com/goo
|
|
|
32
32
|
require "cel"
|
|
33
33
|
|
|
34
34
|
# set the environment
|
|
35
|
-
env = Cel::Environment.new(name: :string, group: :string)
|
|
35
|
+
env = Cel::Environment.new(declarations: { name: :string, group: :string })
|
|
36
36
|
|
|
37
37
|
# 1.1 parse
|
|
38
38
|
begin
|
|
@@ -67,20 +67,52 @@ end
|
|
|
67
67
|
puts return_value #=> true
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
###
|
|
70
|
+
### Environment
|
|
71
71
|
|
|
72
|
-
`
|
|
72
|
+
`Cel::Environment` is the entrypoint for parsing, checking and evaluating CEL expressions, as well as customizing/constraining any of these functions. For that matter, it can be initialized with the following keyword arguments:
|
|
73
|
+
|
|
74
|
+
* `:declarations`: a hash of the expected variables names-to-cel-types, which are used to validate the expression and data bindings. When variables aren't declared, they assume the `any` type.
|
|
75
|
+
* `:container`: used to declare the package of protobuf messages only declared by name used in the expression (i.e. `cel.expr.conformance.proto3`).
|
|
76
|
+
* `:disable_check`: (defaults to `false`) enables/disables expression check phase (except when `env.check(expr)` is explicitly called).
|
|
77
|
+
* `max_recursion_depth`: (defaults to `32`) max number of parsable recursive/repeating rules, as per what the spec states.
|
|
78
|
+
* `max_nesting_depth`: (defaults to `12`) max number of parsable nested rules, as per what the spec states.
|
|
79
|
+
|
|
80
|
+
#### declarations
|
|
81
|
+
|
|
82
|
+
`cel-ruby` supports declaring the types of variables in the environment, which enhances expression type checking:
|
|
73
83
|
|
|
74
84
|
```ruby
|
|
85
|
+
# without declarations
|
|
86
|
+
env = Cel::Environment.new
|
|
87
|
+
env.check("[first_name] + middle_names + [last_name]") #=> any
|
|
88
|
+
|
|
89
|
+
# with declarations
|
|
75
90
|
env = Cel::Environment.new(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
declarations: {
|
|
92
|
+
first_name: :string, # shortcut for Cel::Types[:string]
|
|
93
|
+
middle_names: Cel::TYPES[:list, :string], # list of strings
|
|
94
|
+
last_name: :string
|
|
95
|
+
}
|
|
79
96
|
)
|
|
97
|
+
env.check("[first_name] + middle_names + [last_name]") #=> list(string)
|
|
98
|
+
|
|
99
|
+
# you can use Cel::TYPES to access any type of primitive type, i.e. Cel::TYPES[:bytes]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### :container
|
|
103
|
+
|
|
104
|
+
This can be used to simplify writing CEL expressions with long protobuf package declarations:
|
|
80
105
|
|
|
81
|
-
|
|
106
|
+
```ruby
|
|
107
|
+
env = Cel::Environment.new
|
|
108
|
+
env.evaluate("my.company.private.protobufs.Account{id: 2}.id)")
|
|
109
|
+
# or
|
|
110
|
+
env = Cel::Environment.new(container: "my.company.private.protobufs.")
|
|
111
|
+
env.evaluate("Account{id: 2}.id)")
|
|
82
112
|
```
|
|
83
113
|
|
|
114
|
+
**Note**: the `google.protobuf` packaged is already supported OOTB.
|
|
115
|
+
|
|
84
116
|
### protobuf
|
|
85
117
|
|
|
86
118
|
If `google/protobuf` is available in the environment, `cel-ruby` will also be able to integrate with protobuf declarations in CEL expressions.
|
|
@@ -99,11 +131,11 @@ env.evaluate("google.protobuf.Duration{seconds: 123}.seconds == 123") #=> true
|
|
|
99
131
|
`cel-ruby` allows you to define custom functions to be used insde CEL expressions. While we **strongly** recommend usage of `Cel::Function` for defining them (due to the ability of them being used for checking), the only requirement is that the function object responds to `.call`:
|
|
100
132
|
|
|
101
133
|
```ruby
|
|
102
|
-
env =
|
|
134
|
+
env = Cel::Environment.new(declarations: {foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b }})
|
|
103
135
|
env.evaluate("foo(2, 2)") #=> 4
|
|
104
136
|
|
|
105
137
|
# this is also possible, just not as type-safe
|
|
106
|
-
env2 =
|
|
138
|
+
env2 = Cel::Environment.new(declarations: {foo: ->(a, b) { a + b }})
|
|
107
139
|
env2.evaluate("foo(2, 2)") #=> 4
|
|
108
140
|
```
|
|
109
141
|
|
|
@@ -8,6 +8,25 @@ require "google/protobuf/well_known_types"
|
|
|
8
8
|
|
|
9
9
|
module Cel
|
|
10
10
|
module Protobuf
|
|
11
|
+
PROTOBUF_VALUES = Set.new(
|
|
12
|
+
["Any", "google.protobuf.Any",
|
|
13
|
+
"ListValue", "google.protobuf.ListValue",
|
|
14
|
+
"Struct", "google.protobuf.Struct",
|
|
15
|
+
"Value", "google.protobuf.Value",
|
|
16
|
+
"BoolValue", "google.protobuf.BoolValue",
|
|
17
|
+
"BytesValue", "google.protobuf.BytesValue",
|
|
18
|
+
"DoubleValue", "google.protobuf.DoubleValue",
|
|
19
|
+
"FloatValue", "google.protobuf.FloatValue",
|
|
20
|
+
"Int32Value", "google.protobuf.Int32Value",
|
|
21
|
+
"Int64Value", "google.protobuf.Int64Value",
|
|
22
|
+
"Uint32Value", "google.protobuf.Uint32Value",
|
|
23
|
+
"Uint64Value", "google.protobuf.Uint64Value",
|
|
24
|
+
"NullValue", "google.protobuf.NullValue",
|
|
25
|
+
"StringValue", "google.protobuf.StringValue",
|
|
26
|
+
"Timestamp", "google.protobuf.Timestamp",
|
|
27
|
+
"Duration", "google.protobuf.Duration"]
|
|
28
|
+
)
|
|
29
|
+
|
|
11
30
|
module_function
|
|
12
31
|
|
|
13
32
|
def base_class
|
|
@@ -294,34 +313,18 @@ module Cel
|
|
|
294
313
|
end
|
|
295
314
|
|
|
296
315
|
def try_invoke_from(var, func, args)
|
|
297
|
-
|
|
298
|
-
when "Any", "google.protobuf.Any",
|
|
299
|
-
"ListValue", "google.protobuf.ListValue",
|
|
300
|
-
"Struct", "google.protobuf.Struct",
|
|
301
|
-
"Value", "google.protobuf.Value",
|
|
302
|
-
"BoolValue", "google.protobuf.BoolValue",
|
|
303
|
-
"BytesValue", "google.protobuf.BytesValue",
|
|
304
|
-
"DoubleValue", "google.protobuf.DoubleValue",
|
|
305
|
-
"FloatValue", "google.protobuf.FloatValue",
|
|
306
|
-
"Int32Value", "google.protobuf.Int32Value",
|
|
307
|
-
"Int64Value", "google.protobuf.Int64Value",
|
|
308
|
-
"Uint32Value", "google.protobuf.Uint32Value",
|
|
309
|
-
"Uint64Value", "google.protobuf.Uint64Value",
|
|
310
|
-
"NullValue", "google.protobuf.NullValue",
|
|
311
|
-
"StringValue", "google.protobuf.StringValue",
|
|
312
|
-
"Timestamp", "google.protobuf.Timestamp",
|
|
313
|
-
"Duration", "google.protobuf.Duration"
|
|
314
|
-
protoclass = var.split(".").last
|
|
315
|
-
protoclass = Google::Protobuf.const_get(protoclass)
|
|
316
|
-
|
|
317
|
-
value = if args.nil? && protoclass.constants.include?(func.to_sym)
|
|
318
|
-
protoclass.const_get(func)
|
|
319
|
-
else
|
|
320
|
-
protoclass.__send__(func, *args)
|
|
321
|
-
end
|
|
316
|
+
return unless var.respond_to?(:to_str) && PROTOBUF_VALUES.include?(var.to_str)
|
|
322
317
|
|
|
323
|
-
|
|
318
|
+
protoclass = var.split(".").last
|
|
319
|
+
protoclass = Google::Protobuf.const_get(protoclass)
|
|
320
|
+
|
|
321
|
+
value = if args.nil? && protoclass.constants.include?(func.to_sym)
|
|
322
|
+
protoclass.const_get(func)
|
|
323
|
+
else
|
|
324
|
+
protoclass.__send__(func, *args)
|
|
324
325
|
end
|
|
326
|
+
|
|
327
|
+
Literal.to_cel_type(value)
|
|
325
328
|
end
|
|
326
329
|
|
|
327
330
|
# finds value for +attribute+ declared in the +proto+.
|
data/lib/cel/ast/elements.rb
CHANGED
|
@@ -37,12 +37,13 @@ module Cel
|
|
|
37
37
|
class Message
|
|
38
38
|
EMPTY_STRUCT = {}.freeze
|
|
39
39
|
|
|
40
|
-
attr_reader :name, :struct, :package
|
|
40
|
+
attr_reader :name, :struct, :package, :depth
|
|
41
41
|
|
|
42
|
-
def initialize(name, struct, package: nil)
|
|
42
|
+
def initialize(name, struct, package: nil, depth: 1)
|
|
43
43
|
@struct = struct ? struct.transform_keys(&:to_sym) : EMPTY_STRUCT
|
|
44
44
|
@name = name
|
|
45
45
|
@package = package
|
|
46
|
+
@depth = depth
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def type
|
|
@@ -83,7 +84,7 @@ module Cel
|
|
|
83
84
|
class Invoke
|
|
84
85
|
attr_accessor :var
|
|
85
86
|
|
|
86
|
-
attr_reader :func, :args
|
|
87
|
+
attr_reader :func, :args, :depth
|
|
87
88
|
|
|
88
89
|
def self.new(func:, var: nil, args: nil, **rest)
|
|
89
90
|
Protobuf.try_invoke_from(var, func, args) || super
|
|
@@ -93,11 +94,12 @@ module Cel
|
|
|
93
94
|
TYPES[:any]
|
|
94
95
|
end
|
|
95
96
|
|
|
96
|
-
def initialize(func:, var: nil, args: nil, package: nil)
|
|
97
|
+
def initialize(func:, var: nil, args: nil, depth: 1, package: nil)
|
|
97
98
|
@var = var
|
|
98
99
|
@func = func.to_sym
|
|
99
100
|
@args = args
|
|
100
101
|
@package = package
|
|
102
|
+
@depth = depth
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
def ==(other)
|
|
@@ -113,7 +115,7 @@ module Cel
|
|
|
113
115
|
|
|
114
116
|
def to_s
|
|
115
117
|
if var
|
|
116
|
-
if
|
|
118
|
+
if indexing?
|
|
117
119
|
"#{var}[#{args}]"
|
|
118
120
|
else
|
|
119
121
|
"#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
|
|
@@ -134,6 +136,10 @@ module Cel
|
|
|
134
136
|
|
|
135
137
|
proto_type.const_get(@func)
|
|
136
138
|
end
|
|
139
|
+
|
|
140
|
+
def indexing?
|
|
141
|
+
@func == :[]
|
|
142
|
+
end
|
|
137
143
|
end
|
|
138
144
|
|
|
139
145
|
class Function
|
|
@@ -266,7 +272,7 @@ module Cel
|
|
|
266
272
|
%i[+ -].each do |op|
|
|
267
273
|
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
268
274
|
def #{op}@
|
|
269
|
-
raise
|
|
275
|
+
raise NoOverloadError if @type == TYPES[:uint]
|
|
270
276
|
value = super
|
|
271
277
|
Number.new(@type, value)
|
|
272
278
|
end
|
|
@@ -358,6 +364,10 @@ module Cel
|
|
|
358
364
|
end
|
|
359
365
|
OUT
|
|
360
366
|
end
|
|
367
|
+
|
|
368
|
+
def to_s
|
|
369
|
+
inspect
|
|
370
|
+
end
|
|
361
371
|
end
|
|
362
372
|
|
|
363
373
|
class Bytes < Literal
|
|
@@ -392,11 +402,14 @@ module Cel
|
|
|
392
402
|
end
|
|
393
403
|
|
|
394
404
|
class List < Literal
|
|
395
|
-
|
|
405
|
+
attr_reader :depth
|
|
406
|
+
|
|
407
|
+
def initialize(value, depth: 1)
|
|
396
408
|
value = value.map do |v|
|
|
397
409
|
Literal.to_cel_type(v)
|
|
398
410
|
end
|
|
399
411
|
super(TYPES[:list], value)
|
|
412
|
+
@depth = depth
|
|
400
413
|
end
|
|
401
414
|
|
|
402
415
|
def [](key)
|
|
@@ -429,22 +442,26 @@ module Cel
|
|
|
429
442
|
end
|
|
430
443
|
|
|
431
444
|
def by_max_depth
|
|
432
|
-
max = @value.max { |a1, a2|
|
|
445
|
+
max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
|
|
433
446
|
|
|
434
447
|
# return the last value if all options have the same depth
|
|
435
|
-
return @value.last if (max == @value.first) && (
|
|
448
|
+
return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
|
|
436
449
|
|
|
437
450
|
max
|
|
438
451
|
end
|
|
439
452
|
|
|
453
|
+
def to_s
|
|
454
|
+
"[#{@value.map(&:to_s).join(", ")}]"
|
|
455
|
+
end
|
|
456
|
+
|
|
440
457
|
private
|
|
441
458
|
|
|
442
|
-
def
|
|
459
|
+
def calc_depth(element, acc)
|
|
443
460
|
case element
|
|
444
461
|
when List
|
|
445
|
-
element.value.map { |el|
|
|
462
|
+
element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
446
463
|
when Map
|
|
447
|
-
element.value.map { |(_, el)|
|
|
464
|
+
element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
448
465
|
else
|
|
449
466
|
acc
|
|
450
467
|
end
|
|
@@ -453,13 +470,16 @@ module Cel
|
|
|
453
470
|
|
|
454
471
|
class Map < Literal
|
|
455
472
|
ALLOWED_TYPES = %i[int uint bool string].map { |typ| TYPES[typ] }.freeze
|
|
456
|
-
|
|
473
|
+
|
|
474
|
+
attr_reader :depth
|
|
475
|
+
|
|
476
|
+
def initialize(value, depth: 1)
|
|
457
477
|
# store array internally to support repeated keys
|
|
458
478
|
value = value.map do |k, v|
|
|
459
479
|
[Literal.to_cel_type(k), Literal.to_cel_type(v)]
|
|
460
480
|
end
|
|
461
481
|
super(TYPES[:map], value)
|
|
462
|
-
|
|
482
|
+
@depth = depth
|
|
463
483
|
end
|
|
464
484
|
|
|
465
485
|
def ==(other)
|
|
@@ -507,13 +527,19 @@ module Cel
|
|
|
507
527
|
end
|
|
508
528
|
|
|
509
529
|
def respond_to_missing?(meth, *args)
|
|
510
|
-
|
|
530
|
+
return true if super
|
|
531
|
+
return false unless @value
|
|
532
|
+
|
|
533
|
+
meth_s = meth.to_s
|
|
534
|
+
|
|
535
|
+
@value.any? { |k, _| k == meth_s }
|
|
511
536
|
end
|
|
512
537
|
|
|
513
538
|
def method_missing(meth, *args)
|
|
514
539
|
return super unless @value
|
|
515
540
|
|
|
516
|
-
|
|
541
|
+
meth_s = meth.to_s
|
|
542
|
+
values = @value.filter_map { |k, v| v if k == meth_s }
|
|
517
543
|
|
|
518
544
|
return super if values.empty?
|
|
519
545
|
|
|
@@ -615,6 +641,10 @@ module Cel
|
|
|
615
641
|
Protobuf.timestamp_class.from_time(@value)
|
|
616
642
|
end
|
|
617
643
|
|
|
644
|
+
def to_s
|
|
645
|
+
@value.utc.iso8601
|
|
646
|
+
end
|
|
647
|
+
|
|
618
648
|
private
|
|
619
649
|
|
|
620
650
|
def to_local_time(tz = nil)
|
|
@@ -760,14 +790,15 @@ module Cel
|
|
|
760
790
|
end
|
|
761
791
|
|
|
762
792
|
class Operation
|
|
763
|
-
attr_reader :op, :operands
|
|
793
|
+
attr_reader :op, :operands, :depth
|
|
764
794
|
|
|
765
795
|
attr_accessor :type
|
|
766
796
|
|
|
767
|
-
def initialize(op, operands)
|
|
797
|
+
def initialize(op, operands, depth: 1)
|
|
768
798
|
@op = op
|
|
769
799
|
@operands = operands
|
|
770
800
|
@type = TYPES[:any]
|
|
801
|
+
@depth = depth
|
|
771
802
|
end
|
|
772
803
|
|
|
773
804
|
def ==(other)
|
|
@@ -795,12 +826,13 @@ module Cel
|
|
|
795
826
|
end
|
|
796
827
|
|
|
797
828
|
class Condition
|
|
798
|
-
attr_reader :if, :then, :else
|
|
829
|
+
attr_reader :if, :then, :else, :depth
|
|
799
830
|
|
|
800
|
-
def initialize(if_, then_, else_)
|
|
831
|
+
def initialize(if_, then_, else_, depth: 1)
|
|
801
832
|
@if = if_
|
|
802
833
|
@then = then_
|
|
803
834
|
@else = else_
|
|
835
|
+
@depth = depth
|
|
804
836
|
end
|
|
805
837
|
|
|
806
838
|
def type
|
data/lib/cel/ast/types.rb
CHANGED
|
@@ -176,7 +176,7 @@ module Cel
|
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
def cast(value)
|
|
179
|
-
List.new(value)
|
|
179
|
+
value.is_a?(List) ? value : List.new(value)
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
|
|
@@ -203,7 +203,7 @@ module Cel
|
|
|
203
203
|
end
|
|
204
204
|
|
|
205
205
|
def cast(value)
|
|
206
|
-
Map.new(value)
|
|
206
|
+
value.is_a?(Map) ? value : Map.new(value)
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
|
data/lib/cel/checker.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Cel
|
|
|
21
21
|
timestamp: %i[int string timestamp],
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
|
-
attr_reader :declarations
|
|
24
|
+
attr_reader :environment, :declarations
|
|
25
25
|
|
|
26
26
|
def initialize(environment, declarations = environment.declarations)
|
|
27
27
|
@environment = environment
|
|
@@ -51,6 +51,40 @@ module Cel
|
|
|
51
51
|
|
|
52
52
|
alias_method :call, :check
|
|
53
53
|
|
|
54
|
+
def find_match_all_types(expected, types)
|
|
55
|
+
# at least an expected type must match all values
|
|
56
|
+
type = expected.find do |expected_type|
|
|
57
|
+
case types
|
|
58
|
+
when Array
|
|
59
|
+
types.all? { |typ| typ == expected_type }
|
|
60
|
+
else
|
|
61
|
+
types == expected_type
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
type && types.is_a?(Type) ? types : TYPES[type]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def check_arity(func, args, arity, op = :===)
|
|
69
|
+
return if arity.__send__(op, args.size)
|
|
70
|
+
|
|
71
|
+
raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def check_arity_any(func, args)
|
|
75
|
+
return if args.size.positive?
|
|
76
|
+
|
|
77
|
+
raise CheckError, "`#{func}` invoked with no arguments"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def unsupported_type(op)
|
|
81
|
+
raise CheckError, "no matching overload: #{op}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def unsupported_operation(op)
|
|
85
|
+
raise CheckError, "unsupported operation (#{op})"
|
|
86
|
+
end
|
|
87
|
+
|
|
54
88
|
private
|
|
55
89
|
|
|
56
90
|
def merge(declarations)
|
|
@@ -194,6 +228,10 @@ module Cel
|
|
|
194
228
|
|
|
195
229
|
return check_standard_func(funcall) unless var
|
|
196
230
|
|
|
231
|
+
if var.is_a?(Identifier) && Cel::EXTENSIONS.include?(var.to_sym)
|
|
232
|
+
return Cel::EXTENSIONS[var.to_sym].__check(funcall, checker: self)
|
|
233
|
+
end
|
|
234
|
+
|
|
197
235
|
var_type ||= infer_variable_type(var)
|
|
198
236
|
|
|
199
237
|
case var_type
|
|
@@ -283,6 +321,8 @@ module Cel
|
|
|
283
321
|
var_type.element_type = element_checker.check(predicate)
|
|
284
322
|
var_type
|
|
285
323
|
else
|
|
324
|
+
return Cel::EXTENSIONS[:strings].__check(funcall, checker: self) if Cel::EXTENSIONS.key?(:strings)
|
|
325
|
+
|
|
286
326
|
unsupported_operation(funcall)
|
|
287
327
|
end
|
|
288
328
|
when TYPES[:string]
|
|
@@ -295,6 +335,8 @@ module Cel
|
|
|
295
335
|
# TODO: verify if string can be transformed into a regex
|
|
296
336
|
return TYPES[:bool] if find_match_all_types(%i[string], call(args.first))
|
|
297
337
|
else
|
|
338
|
+
return Cel::EXTENSIONS[:strings].__check(funcall, checker: self) if Cel::EXTENSIONS.key?(:strings)
|
|
339
|
+
|
|
298
340
|
unsupported_type(funcall)
|
|
299
341
|
end
|
|
300
342
|
unsupported_operation(funcall)
|
|
@@ -373,7 +415,7 @@ module Cel
|
|
|
373
415
|
|
|
374
416
|
arg = call(args.first)
|
|
375
417
|
|
|
376
|
-
return TYPES[func] if find_match_all_types(allowed_types, arg)
|
|
418
|
+
return TYPES[func] if find_match_all_types(allowed_types, arg) || arg == TYPES[:any]
|
|
377
419
|
when :matches
|
|
378
420
|
check_arity(func, args, 2)
|
|
379
421
|
return TYPES[:bool] if find_match_all_types(%i[string], args.map(&method(:call)))
|
|
@@ -424,7 +466,9 @@ module Cel
|
|
|
424
466
|
def check_identifier(identifier)
|
|
425
467
|
return identifier.type unless identifier.type == :any
|
|
426
468
|
|
|
427
|
-
|
|
469
|
+
id_sym = identifier.to_sym
|
|
470
|
+
|
|
471
|
+
return TYPES[:type] if Cel::PRIMITIVE_TYPES.include?(id_sym)
|
|
428
472
|
|
|
429
473
|
proto_type = identifier.try_convert_to_proto_type
|
|
430
474
|
|
|
@@ -472,33 +516,5 @@ module Cel
|
|
|
472
516
|
typ
|
|
473
517
|
end
|
|
474
518
|
end
|
|
475
|
-
|
|
476
|
-
def find_match_all_types(expected, types)
|
|
477
|
-
# at least an expected type must match all values
|
|
478
|
-
type = expected.find do |expected_type|
|
|
479
|
-
case types
|
|
480
|
-
when Array
|
|
481
|
-
types.all? { |typ| typ == expected_type }
|
|
482
|
-
else
|
|
483
|
-
types == expected_type
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
type && types.is_a?(Type) ? types : TYPES[type]
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
def check_arity(func, args, arity)
|
|
491
|
-
return if arity === args.size # rubocop:disable Style/CaseEquality
|
|
492
|
-
|
|
493
|
-
raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
def unsupported_type(op)
|
|
497
|
-
raise CheckError, "no matching overload: #{op}"
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
def unsupported_operation(op)
|
|
501
|
-
raise CheckError, "unsupported operation (#{op})"
|
|
502
|
-
end
|
|
503
519
|
end
|
|
504
520
|
end
|
data/lib/cel/context.rb
CHANGED
|
@@ -2,28 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Cel
|
|
4
4
|
class Context
|
|
5
|
-
attr_reader :declarations
|
|
5
|
+
attr_reader :declarations, :bindings
|
|
6
6
|
|
|
7
7
|
def initialize(declarations, bindings)
|
|
8
8
|
@declarations = declarations
|
|
9
9
|
@bindings = bindings.dup
|
|
10
|
-
|
|
11
|
-
return unless @bindings
|
|
12
|
-
|
|
13
|
-
@bindings.each do |k, v|
|
|
14
|
-
if @declarations && (type = @declarations[k])
|
|
15
|
-
type = TYPES[type] if type.is_a?(Symbol)
|
|
16
|
-
|
|
17
|
-
val = if type.is_a?(Class) && type < Protobuf.base_class
|
|
18
|
-
Protobuf.convert_to_proto(type, v)
|
|
19
|
-
else
|
|
20
|
-
type.cast(to_cel_type(v))
|
|
21
|
-
end
|
|
22
|
-
else
|
|
23
|
-
val = to_cel_type(v)
|
|
24
|
-
end
|
|
25
|
-
@bindings[k] = val
|
|
26
|
-
end
|
|
27
10
|
end
|
|
28
11
|
|
|
29
12
|
def lookup(identifier)
|
|
@@ -39,6 +22,7 @@ module Cel
|
|
|
39
22
|
fetched = false
|
|
40
23
|
(0...lookup_keys.size).reverse_each do |idx|
|
|
41
24
|
key = lookup_keys[0..idx].join(".").to_sym
|
|
25
|
+
is_binding = val == @bindings
|
|
42
26
|
|
|
43
27
|
val = if val.respond_to?(:key?)
|
|
44
28
|
# hash
|
|
@@ -56,7 +40,8 @@ module Cel
|
|
|
56
40
|
end
|
|
57
41
|
end
|
|
58
42
|
|
|
59
|
-
|
|
43
|
+
val = @bindings[key] = to_cel_primitive(key, val) if is_binding
|
|
44
|
+
lookup_keys = lookup_keys[(idx + 1)..]
|
|
60
45
|
fetched = true
|
|
61
46
|
break
|
|
62
47
|
end
|
|
@@ -86,5 +71,19 @@ module Cel
|
|
|
86
71
|
def to_cel_type(v)
|
|
87
72
|
Literal.to_cel_type(v)
|
|
88
73
|
end
|
|
74
|
+
|
|
75
|
+
def to_cel_primitive(k, v)
|
|
76
|
+
if @declarations && (type = @declarations[k])
|
|
77
|
+
type = TYPES[type] if type.is_a?(Symbol)
|
|
78
|
+
|
|
79
|
+
if type.is_a?(Class) && type < Protobuf.base_class
|
|
80
|
+
Protobuf.convert_to_proto(type, v)
|
|
81
|
+
else
|
|
82
|
+
type.cast(to_cel_type(v))
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
to_cel_type(v)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
89
88
|
end
|
|
90
89
|
end
|
data/lib/cel/environment.rb
CHANGED
|
@@ -4,7 +4,12 @@ module Cel
|
|
|
4
4
|
class Environment
|
|
5
5
|
attr_reader :declarations, :package
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
7
|
+
def initialize(
|
|
8
|
+
declarations: nil,
|
|
9
|
+
container: nil,
|
|
10
|
+
disable_check: false,
|
|
11
|
+
**kwargs
|
|
12
|
+
)
|
|
8
13
|
@disable_check = disable_check
|
|
9
14
|
@declarations = declarations
|
|
10
15
|
@container = container
|
|
@@ -28,7 +33,7 @@ module Cel
|
|
|
28
33
|
@package = Object.const_get(proto_module_name) if Object.const_defined?(proto_module_name)
|
|
29
34
|
end
|
|
30
35
|
|
|
31
|
-
@parser = Parser.new(package)
|
|
36
|
+
@parser = Parser.new(package, **kwargs)
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
def compile(expr)
|
|
@@ -48,8 +53,8 @@ module Cel
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def check(expr)
|
|
51
|
-
|
|
52
|
-
@checker.check(
|
|
56
|
+
expr = @parser.parse(expr) if expr.is_a?(::String)
|
|
57
|
+
@checker.check(expr)
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def program(expr)
|
|
@@ -86,6 +91,19 @@ module Cel
|
|
|
86
91
|
[declarations, bindings]
|
|
87
92
|
end
|
|
88
93
|
|
|
94
|
+
def with(declarations:, disable_check: @disable_check)
|
|
95
|
+
prev_declarations = @declarations
|
|
96
|
+
prev_disable_check = @disable_check
|
|
97
|
+
|
|
98
|
+
@declarations = declarations.merge(declarations)
|
|
99
|
+
@disable_check = disable_check
|
|
100
|
+
|
|
101
|
+
yield
|
|
102
|
+
ensure
|
|
103
|
+
@declarations = prev_declarations
|
|
104
|
+
@disable_check = prev_disable_check
|
|
105
|
+
end
|
|
106
|
+
|
|
89
107
|
private
|
|
90
108
|
|
|
91
109
|
def validate(ast, structs); end
|