cel 0.3.0 → 0.3.1
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 +11 -0
- data/README.md +39 -7
- data/lib/cel/ast/elements/protobuf.rb +29 -26
- data/lib/cel/ast/elements.rb +39 -19
- data/lib/cel/ast/types.rb +2 -2
- data/lib/cel/context.rb +17 -18
- data/lib/cel/environment.rb +7 -2
- data/lib/cel/errors.rb +3 -0
- data/lib/cel/macro.rb +4 -3
- data/lib/cel/parser.rb +207 -23
- data/lib/cel/program.rb +20 -13
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82fcb37e45c0553a7c073b58e4c6b7203cc50bd186419724d72f5f1b0217e3f5
|
4
|
+
data.tar.gz: 44170fc7001634abc19860db8dcbc8c05ae8974716ad80dd28c4316dc0cec7f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 593ec20b7001fe593d6df79928ca8411327251fff2405491eb33642299cf6bc09d255d0088fdb6a1deda77eabccf8bcce229ad5038f68ee932f9c10ef162cdc0
|
7
|
+
data.tar.gz: 55eb21528a3ff994f5fc013ca49ded2b7cb0aaf9d5d434c671fc0cd534ed0119269c1e2bf8b5fe9e44ba1f1e05f51d30f5fd84abd1f44d740898dd971b341ece
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.1] - 2025-08-01
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* 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))`).
|
8
|
+
* lazy-evaluating bindings to avoid upfront costly transformations of ruby objects into cel objects which are ultimately not used in expressions.
|
9
|
+
|
10
|
+
### Bugfixes
|
11
|
+
|
12
|
+
* `has()` macro correctly works for mapsl
|
13
|
+
|
3
14
|
## [0.3.0] - 2025-06-12
|
4
15
|
|
5
16
|
### 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.
|
@@ -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
|
@@ -392,11 +398,14 @@ module Cel
|
|
392
398
|
end
|
393
399
|
|
394
400
|
class List < Literal
|
395
|
-
|
401
|
+
attr_reader :depth
|
402
|
+
|
403
|
+
def initialize(value, depth: 1)
|
396
404
|
value = value.map do |v|
|
397
405
|
Literal.to_cel_type(v)
|
398
406
|
end
|
399
407
|
super(TYPES[:list], value)
|
408
|
+
@depth = depth
|
400
409
|
end
|
401
410
|
|
402
411
|
def [](key)
|
@@ -429,22 +438,22 @@ module Cel
|
|
429
438
|
end
|
430
439
|
|
431
440
|
def by_max_depth
|
432
|
-
max = @value.max { |a1, a2|
|
441
|
+
max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
|
433
442
|
|
434
443
|
# return the last value if all options have the same depth
|
435
|
-
return @value.last if (max == @value.first) && (
|
444
|
+
return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
|
436
445
|
|
437
446
|
max
|
438
447
|
end
|
439
448
|
|
440
449
|
private
|
441
450
|
|
442
|
-
def
|
451
|
+
def calc_depth(element, acc)
|
443
452
|
case element
|
444
453
|
when List
|
445
|
-
element.value.map { |el|
|
454
|
+
element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
|
446
455
|
when Map
|
447
|
-
element.value.map { |(_, el)|
|
456
|
+
element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
|
448
457
|
else
|
449
458
|
acc
|
450
459
|
end
|
@@ -453,13 +462,16 @@ module Cel
|
|
453
462
|
|
454
463
|
class Map < Literal
|
455
464
|
ALLOWED_TYPES = %i[int uint bool string].map { |typ| TYPES[typ] }.freeze
|
456
|
-
|
465
|
+
|
466
|
+
attr_reader :depth
|
467
|
+
|
468
|
+
def initialize(value, depth: 1)
|
457
469
|
# store array internally to support repeated keys
|
458
470
|
value = value.map do |k, v|
|
459
471
|
[Literal.to_cel_type(k), Literal.to_cel_type(v)]
|
460
472
|
end
|
461
473
|
super(TYPES[:map], value)
|
462
|
-
|
474
|
+
@depth = depth
|
463
475
|
end
|
464
476
|
|
465
477
|
def ==(other)
|
@@ -507,13 +519,19 @@ module Cel
|
|
507
519
|
end
|
508
520
|
|
509
521
|
def respond_to_missing?(meth, *args)
|
510
|
-
|
522
|
+
return true if super
|
523
|
+
return false unless @value
|
524
|
+
|
525
|
+
meth_s = meth.to_s
|
526
|
+
|
527
|
+
@value.any? { |k, _| k == meth_s }
|
511
528
|
end
|
512
529
|
|
513
530
|
def method_missing(meth, *args)
|
514
531
|
return super unless @value
|
515
532
|
|
516
|
-
|
533
|
+
meth_s = meth.to_s
|
534
|
+
values = @value.filter_map { |k, v| v if k == meth_s }
|
517
535
|
|
518
536
|
return super if values.empty?
|
519
537
|
|
@@ -760,14 +778,15 @@ module Cel
|
|
760
778
|
end
|
761
779
|
|
762
780
|
class Operation
|
763
|
-
attr_reader :op, :operands
|
781
|
+
attr_reader :op, :operands, :depth
|
764
782
|
|
765
783
|
attr_accessor :type
|
766
784
|
|
767
|
-
def initialize(op, operands)
|
785
|
+
def initialize(op, operands, depth: 1)
|
768
786
|
@op = op
|
769
787
|
@operands = operands
|
770
788
|
@type = TYPES[:any]
|
789
|
+
@depth = depth
|
771
790
|
end
|
772
791
|
|
773
792
|
def ==(other)
|
@@ -795,12 +814,13 @@ module Cel
|
|
795
814
|
end
|
796
815
|
|
797
816
|
class Condition
|
798
|
-
attr_reader :if, :then, :else
|
817
|
+
attr_reader :if, :then, :else, :depth
|
799
818
|
|
800
|
-
def initialize(if_, then_, else_)
|
819
|
+
def initialize(if_, then_, else_, depth: 1)
|
801
820
|
@if = if_
|
802
821
|
@then = then_
|
803
822
|
@else = else_
|
823
|
+
@depth = depth
|
804
824
|
end
|
805
825
|
|
806
826
|
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/context.rb
CHANGED
@@ -7,23 +7,6 @@ module Cel
|
|
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)
|
data/lib/cel/errors.rb
CHANGED
data/lib/cel/macro.rb
CHANGED
@@ -13,12 +13,13 @@ module Cel
|
|
13
13
|
# If f is some other singular field, has(e.f) indicates whether the field's value is its default
|
14
14
|
# value (zero for numeric fields, false for booleans, empty for strings and bytes).
|
15
15
|
def has(invoke, program:)
|
16
|
-
var = invoke.var
|
17
16
|
func = invoke.func
|
17
|
+
var = program.disable_cel_conversion do
|
18
|
+
program.evaluate(invoke.var)
|
19
|
+
end
|
18
20
|
|
19
21
|
case var
|
20
|
-
when
|
21
|
-
var = program.evaluate(var)
|
22
|
+
when Protobuf.base_class
|
22
23
|
# If e evaluates to a message and f is not a declared field for the message,
|
23
24
|
# has(e.f) raises a no_such_field error.
|
24
25
|
raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)
|
data/lib/cel/parser.rb
CHANGED
@@ -89,7 +89,14 @@ var void while
|
|
89
89
|
|
90
90
|
IDENTIFIER_REGEX = /[_a-zA-Z][_a-zA-Z0-9]*/
|
91
91
|
|
92
|
-
def initialize(
|
92
|
+
def initialize(
|
93
|
+
package = nil,
|
94
|
+
max_recursion_depth: 32,
|
95
|
+
max_nesting_depth: 12,
|
96
|
+
expression_size_limit: 2048
|
97
|
+
)
|
98
|
+
@max_recursion_depth = max_recursion_depth
|
99
|
+
@max_nesting_depth = max_nesting_depth
|
93
100
|
@package = package
|
94
101
|
end
|
95
102
|
|
@@ -243,12 +250,49 @@ def literal_null
|
|
243
250
|
Cel::Null::INSTANCE
|
244
251
|
end
|
245
252
|
|
246
|
-
def new_map(
|
247
|
-
|
253
|
+
def new_map(elements)
|
254
|
+
depth = 1
|
255
|
+
|
256
|
+
if elements.size > @max_recursion_depth
|
257
|
+
raise MaxRecursionDepthExceededError, "max number of map elements exceeded"
|
258
|
+
end
|
259
|
+
|
260
|
+
depth = elements.filter_map do |_, elem|
|
261
|
+
next unless elem.is_a?(Cel::Map)
|
262
|
+
|
263
|
+
d = depth + elem.depth
|
264
|
+
|
265
|
+
if d > @max_nesting_depth
|
266
|
+
raise MaxNestingDepthExceededError, "max nesting of map literals exceeded"
|
267
|
+
end
|
268
|
+
|
269
|
+
d
|
270
|
+
end.max || depth
|
271
|
+
|
272
|
+
Cel::Map.new(elements, depth: depth)
|
248
273
|
end
|
249
274
|
|
250
275
|
def new_list(val)
|
251
|
-
|
276
|
+
elements = Array(val)
|
277
|
+
depth = 1
|
278
|
+
|
279
|
+
if elements.size > @max_recursion_depth
|
280
|
+
raise MaxRecursionDepthExceededError, "max number of elements for list literals exceeded"
|
281
|
+
end
|
282
|
+
|
283
|
+
depth = elements.filter_map do |elem|
|
284
|
+
next unless elem.is_a?(Cel::List)
|
285
|
+
|
286
|
+
d = depth + elem.depth
|
287
|
+
|
288
|
+
if d > @max_nesting_depth
|
289
|
+
raise MaxNestingDepthExceededError, "max nesting of list literals exceeded"
|
290
|
+
end
|
291
|
+
|
292
|
+
d
|
293
|
+
end.max || depth
|
294
|
+
|
295
|
+
Cel::List.new(elements, depth: depth)
|
252
296
|
end
|
253
297
|
|
254
298
|
def new_identifier(id)
|
@@ -256,19 +300,159 @@ def new_identifier(id)
|
|
256
300
|
end
|
257
301
|
|
258
302
|
def new_message(type, struct)
|
259
|
-
|
303
|
+
depth = 1
|
304
|
+
if struct
|
305
|
+
if struct.size > @max_recursion_depth
|
306
|
+
raise MaxRecursionDepthExceededError, "max number of message elements exceeded"
|
307
|
+
end
|
308
|
+
|
309
|
+
depth = struct.filter_map do |_, elem|
|
310
|
+
next unless elem.is_a?(Cel::Message)
|
311
|
+
|
312
|
+
d = depth + elem.depth
|
313
|
+
|
314
|
+
if d > @max_nesting_depth
|
315
|
+
raise MaxNestingDepthExceededError, "max nesting of map literals exceeded"
|
316
|
+
end
|
317
|
+
|
318
|
+
d
|
319
|
+
end.max || depth
|
320
|
+
end
|
321
|
+
|
322
|
+
Cel::Message.new(type, struct, package: @package, depth: depth)
|
260
323
|
end
|
261
324
|
|
262
325
|
def global_call(function, args = nil)
|
263
|
-
|
326
|
+
depth = 1
|
327
|
+
|
328
|
+
if args && args.size > @max_recursion_depth
|
329
|
+
raise MaxRecursionDepthExceededError, "max number of function call arguments exceeded"
|
330
|
+
end
|
331
|
+
|
332
|
+
depth = args.filter_map do |arg|
|
333
|
+
next unless arg.is_a?(Cel::Invoke)
|
334
|
+
|
335
|
+
d = depth + arg.depth
|
336
|
+
|
337
|
+
if d > @max_nesting_depth
|
338
|
+
raise MaxNestingDepthExceededError, "max nesting of function calls exceeded"
|
339
|
+
end
|
340
|
+
|
341
|
+
d
|
342
|
+
end.max || depth
|
343
|
+
|
344
|
+
Cel::Invoke.new(func: function, var: nil, args: args, package: @package, depth: depth)
|
264
345
|
end
|
265
346
|
|
266
|
-
def
|
267
|
-
|
347
|
+
def indexing_call(target, args)
|
348
|
+
depth = 1
|
349
|
+
|
350
|
+
if target.is_a?(Cel::Invoke)
|
351
|
+
depth += target.depth if target.indexing?
|
352
|
+
|
353
|
+
if depth > @max_nesting_depth
|
354
|
+
raise MaxNestingDepthExceededError, "max nesting of indexing operators exceeded"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
receiver_call(:[], target, args, package: @package, depth: depth)
|
268
359
|
end
|
269
360
|
|
270
|
-
def
|
271
|
-
|
361
|
+
def selector_call(function, target)
|
362
|
+
depth = 1
|
363
|
+
|
364
|
+
# if selector operator
|
365
|
+
if target.is_a?(Cel::Invoke) && !target.var.nil?
|
366
|
+
depth += target.depth
|
367
|
+
|
368
|
+
if depth > @max_nesting_depth
|
369
|
+
raise MaxNestingDepthExceededError, "max nesting of selection operators exceeded"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
receiver_call(function, target, package: @package, depth: depth)
|
374
|
+
end
|
375
|
+
|
376
|
+
def receiver_call(function, target, args = nil, **kwargs)
|
377
|
+
Cel::Invoke.new(func: function, var: target, args: args, package: @package, **kwargs)
|
378
|
+
end
|
379
|
+
|
380
|
+
def logical_operation(op, operands)
|
381
|
+
lhs, rhs = operands
|
382
|
+
|
383
|
+
if lhs.is_a?(Cel::Operation) && lhs.op == op
|
384
|
+
# concat them
|
385
|
+
operands = [*lhs.operands, rhs]
|
386
|
+
end
|
387
|
+
|
388
|
+
if operands.size - 1 > @max_recursion_depth
|
389
|
+
raise Cel::MaxRecursionDepthExceededError, "max recursion depth exceeded for #{op} operation"
|
390
|
+
end
|
391
|
+
|
392
|
+
operation(op, operands)
|
393
|
+
end
|
394
|
+
|
395
|
+
def math_operation(op, operands)
|
396
|
+
# 32 binary arithmetic operators of the same precedence in a row
|
397
|
+
lhs, _ = operands
|
398
|
+
depth = 1
|
399
|
+
|
400
|
+
if lhs.is_a?(Cel::Operation)
|
401
|
+
case op
|
402
|
+
when "*", "/", "%"
|
403
|
+
case lhs.op
|
404
|
+
when "*", "/", "%"
|
405
|
+
depth = lhs.depth + 1
|
406
|
+
end
|
407
|
+
when "+", "+"
|
408
|
+
case lhs.op
|
409
|
+
when "+", "+"
|
410
|
+
depth = lhs.depth + 1
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
if depth > @max_recursion_depth
|
416
|
+
raise MaxRecursionDepthExceededError, "max number of arithmetic operators with the same precedence exceeded"
|
417
|
+
end
|
418
|
+
|
419
|
+
operation(op, operands, depth: depth)
|
420
|
+
end
|
421
|
+
|
422
|
+
def unary_operation(op, operand)
|
423
|
+
depth = 1
|
424
|
+
|
425
|
+
if operand.is_a?(Cel::Operation) && operand.op == op && operand.unary?
|
426
|
+
depth = depth + operand.depth
|
427
|
+
|
428
|
+
if depth > @max_recursion_depth
|
429
|
+
raise MaxRecursionDepthExceededError, "max number of an unary operator exceeded"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
operation(op, [operand], depth: depth)
|
434
|
+
end
|
435
|
+
|
436
|
+
def operation(op, operands, **kwargs)
|
437
|
+
Cel::Operation.new(op, operands, **kwargs)
|
438
|
+
end
|
439
|
+
|
440
|
+
def ternary_condition(if_op, *ops)
|
441
|
+
depth = 1
|
442
|
+
|
443
|
+
depth = ops.filter_map do |op|
|
444
|
+
next unless op.is_a?(Cel::Condition)
|
445
|
+
|
446
|
+
d = depth + op.depth
|
447
|
+
|
448
|
+
if d > @max_recursion_depth
|
449
|
+
raise MaxRecursionDepthExceededError, "max number of consecutive ternary operators exceeded"
|
450
|
+
end
|
451
|
+
|
452
|
+
d
|
453
|
+
end.max || depth
|
454
|
+
|
455
|
+
Cel::Condition.new(if_op, *ops, depth: depth)
|
272
456
|
end
|
273
457
|
|
274
458
|
# Checks whether the given identifier token is a reserved word or not. Throws
|
@@ -276,7 +460,7 @@ end
|
|
276
460
|
def validated_id!(identifier)
|
277
461
|
return identifier unless RESERVED.include?(identifier)
|
278
462
|
|
279
|
-
raise Cel::ParseError
|
463
|
+
raise Cel::ParseError, "invalid usage of the reserved word \"#{identifier}\""
|
280
464
|
end
|
281
465
|
|
282
466
|
...end parser.ry/module_eval...
|
@@ -646,7 +830,7 @@ Racc_debug_parser = false
|
|
646
830
|
|
647
831
|
module_eval(<<'.,.,', 'parser.ry', 17)
|
648
832
|
def _reduce_4(val, _values, result)
|
649
|
-
result =
|
833
|
+
result = ternary_condition(val[0], val[2], val[4])
|
650
834
|
result
|
651
835
|
end
|
652
836
|
.,.,
|
@@ -655,7 +839,7 @@ module_eval(<<'.,.,', 'parser.ry', 17)
|
|
655
839
|
|
656
840
|
module_eval(<<'.,.,', 'parser.ry', 20)
|
657
841
|
def _reduce_6(val, _values, result)
|
658
|
-
result =
|
842
|
+
result = logical_operation(val[1], [val[0], val[2]])
|
659
843
|
result
|
660
844
|
end
|
661
845
|
.,.,
|
@@ -664,7 +848,7 @@ module_eval(<<'.,.,', 'parser.ry', 20)
|
|
664
848
|
|
665
849
|
module_eval(<<'.,.,', 'parser.ry', 23)
|
666
850
|
def _reduce_8(val, _values, result)
|
667
|
-
result =
|
851
|
+
result = logical_operation(val[1], [val[0], val[2]])
|
668
852
|
result
|
669
853
|
end
|
670
854
|
.,.,
|
@@ -682,14 +866,14 @@ module_eval(<<'.,.,', 'parser.ry', 26)
|
|
682
866
|
|
683
867
|
module_eval(<<'.,.,', 'parser.ry', 29)
|
684
868
|
def _reduce_12(val, _values, result)
|
685
|
-
result =
|
869
|
+
result = math_operation(val[1], [val[0], val[2]])
|
686
870
|
result
|
687
871
|
end
|
688
872
|
.,.,
|
689
873
|
|
690
874
|
module_eval(<<'.,.,', 'parser.ry', 30)
|
691
875
|
def _reduce_13(val, _values, result)
|
692
|
-
result =
|
876
|
+
result = math_operation(val[1], [val[0], val[2]])
|
693
877
|
result
|
694
878
|
end
|
695
879
|
.,.,
|
@@ -698,7 +882,7 @@ module_eval(<<'.,.,', 'parser.ry', 30)
|
|
698
882
|
|
699
883
|
module_eval(<<'.,.,', 'parser.ry', 33)
|
700
884
|
def _reduce_15(val, _values, result)
|
701
|
-
result =
|
885
|
+
result = math_operation(val[1], [val[0], val[2]])
|
702
886
|
result
|
703
887
|
end
|
704
888
|
.,.,
|
@@ -709,21 +893,21 @@ module_eval(<<'.,.,', 'parser.ry', 33)
|
|
709
893
|
|
710
894
|
module_eval(<<'.,.,', 'parser.ry', 38)
|
711
895
|
def _reduce_18(val, _values, result)
|
712
|
-
result =
|
896
|
+
result = unary_operation("!", val[1])
|
713
897
|
result
|
714
898
|
end
|
715
899
|
.,.,
|
716
900
|
|
717
901
|
module_eval(<<'.,.,', 'parser.ry', 39)
|
718
902
|
def _reduce_19(val, _values, result)
|
719
|
-
result =
|
903
|
+
result = unary_operation("-", val[1])
|
720
904
|
result
|
721
905
|
end
|
722
906
|
.,.,
|
723
907
|
|
724
908
|
module_eval(<<'.,.,', 'parser.ry', 41)
|
725
909
|
def _reduce_20(val, _values, result)
|
726
|
-
result =
|
910
|
+
result = unary_operation("!", val[1])
|
727
911
|
result
|
728
912
|
end
|
729
913
|
.,.,
|
@@ -732,7 +916,7 @@ module_eval(<<'.,.,', 'parser.ry', 41)
|
|
732
916
|
|
733
917
|
module_eval(<<'.,.,', 'parser.ry', 44)
|
734
918
|
def _reduce_22(val, _values, result)
|
735
|
-
result =
|
919
|
+
result = unary_operation("-", val[1])
|
736
920
|
result
|
737
921
|
end
|
738
922
|
.,.,
|
@@ -743,7 +927,7 @@ module_eval(<<'.,.,', 'parser.ry', 44)
|
|
743
927
|
|
744
928
|
module_eval(<<'.,.,', 'parser.ry', 48)
|
745
929
|
def _reduce_25(val, _values, result)
|
746
|
-
result =
|
930
|
+
result = indexing_call(val[0], val[2])
|
747
931
|
result
|
748
932
|
end
|
749
933
|
.,.,
|
@@ -757,7 +941,7 @@ module_eval(<<'.,.,', 'parser.ry', 49)
|
|
757
941
|
|
758
942
|
module_eval(<<'.,.,', 'parser.ry', 50)
|
759
943
|
def _reduce_27(val, _values, result)
|
760
|
-
result =
|
944
|
+
result = selector_call(val[2], val[0])
|
761
945
|
result
|
762
946
|
end
|
763
947
|
.,.,
|
data/lib/cel/program.rb
CHANGED
@@ -35,6 +35,16 @@ module Cel
|
|
35
35
|
|
36
36
|
alias_method :call, :evaluate
|
37
37
|
|
38
|
+
def disable_cel_conversion
|
39
|
+
convert_to_cel = @convert_to_cel
|
40
|
+
begin
|
41
|
+
@convert_to_cel = false
|
42
|
+
yield(convert_to_cel)
|
43
|
+
ensure
|
44
|
+
@convert_to_cel = convert_to_cel
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
38
48
|
private
|
39
49
|
|
40
50
|
def try_evaluate_lookup(key)
|
@@ -68,21 +78,18 @@ module Cel
|
|
68
78
|
end
|
69
79
|
|
70
80
|
def evaluate_message(val)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
value = Protobuf.convert_to_proto(val.message_type, values)
|
81
|
+
disable_cel_conversion do |should_have_converted|
|
82
|
+
values = val.struct.transform_values do |value|
|
83
|
+
v = call(value)
|
84
|
+
v = v.to_ruby_type if v.respond_to?(:to_ruby_type)
|
85
|
+
v
|
86
|
+
end
|
87
|
+
value = Protobuf.convert_to_proto(val.message_type, values)
|
80
88
|
|
81
|
-
|
89
|
+
return value unless should_have_converted
|
82
90
|
|
83
|
-
|
84
|
-
|
85
|
-
@convert_to_cel = convert_to_cel
|
91
|
+
Literal.to_cel_type(value)
|
92
|
+
end
|
86
93
|
end
|
87
94
|
|
88
95
|
def evaluate_operation(operation)
|
data/lib/cel/version.rb
CHANGED
data/lib/cel.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: bigdecimal
|
@@ -57,8 +57,8 @@ email:
|
|
57
57
|
executables: []
|
58
58
|
extensions: []
|
59
59
|
extra_rdoc_files:
|
60
|
-
- LICENSE.txt
|
61
60
|
- CHANGELOG.md
|
61
|
+
- LICENSE.txt
|
62
62
|
- README.md
|
63
63
|
files:
|
64
64
|
- CHANGELOG.md
|
@@ -99,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
99
|
- !ruby/object:Gem::Version
|
100
100
|
version: '0'
|
101
101
|
requirements: []
|
102
|
-
rubygems_version: 3.6.
|
102
|
+
rubygems_version: 3.6.7
|
103
103
|
specification_version: 4
|
104
104
|
summary: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
|
105
105
|
test_files: []
|