cel 0.4.0 → 0.4.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 +18 -1
- data/README.md +24 -1
- data/lib/cel/ast/cel_methods.rb +58 -0
- data/lib/cel/ast/elements/bool.rb +34 -0
- data/lib/cel/ast/elements/bytes.rb +34 -0
- data/lib/cel/ast/elements/condition.rb +22 -0
- data/lib/cel/ast/elements/duration.rb +114 -0
- data/lib/cel/ast/elements/function.rb +33 -0
- data/lib/cel/ast/elements/group.rb +19 -0
- data/lib/cel/ast/elements/identifier.rb +32 -0
- data/lib/cel/ast/elements/invoke.rb +64 -0
- data/lib/cel/ast/elements/list.rb +70 -0
- data/lib/cel/ast/elements/literal.rb +74 -0
- data/lib/cel/ast/elements/map.rb +120 -0
- data/lib/cel/ast/elements/message.rb +50 -0
- data/lib/cel/ast/elements/null.rb +11 -0
- data/lib/cel/ast/elements/number.rb +70 -0
- data/lib/cel/ast/elements/operation.rb +39 -0
- data/lib/cel/ast/elements/protobuf.rb +4 -0
- data/lib/cel/ast/elements/string.rb +54 -0
- data/lib/cel/ast/elements/timestamp.rb +109 -0
- data/lib/cel/ast/elements.rb +18 -840
- data/lib/cel/ast/types.rb +49 -4
- data/lib/cel/errors.rb +2 -0
- data/lib/cel/extensions/string.rb +18 -16
- data/lib/cel/extensions.rb +4 -4
- data/lib/cel/parser.rb +12 -11
- data/lib/cel/program.rb +16 -9
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +27 -13
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4d35d9012196d61497f40e35c8e9dd9b1a9d7e1e922078a414cd574eacc439a
|
|
4
|
+
data.tar.gz: f2a0e48bc07e0c0b5eb23c7ea6d709a1b56fff3400b51204d26c3031f11db696
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d39c028b26513ed27749f5bd7c5cfa57281b0433a5b5984418d657fd61441122e4cddae1942cdb7b3e3c9aeb0979d7387ca5d7444cea0afd04477615278acbfa
|
|
7
|
+
data.tar.gz: 442da2f399685e77bee5f743c693f0ef0fe9b333dfea333f61b17f8419a6bf7752aca14e0c5211ff0a8a526bc36a9a575a9e0cac53190f47e3af0b54097c26de
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.1] - 2025-11-25
|
|
4
|
+
|
|
5
|
+
### Improvements
|
|
6
|
+
|
|
7
|
+
* Literal class can now mark which methods are CEL directives, the remainder being lib private helpers.
|
|
8
|
+
* `cel` is now ractor compatible.
|
|
9
|
+
* Documentation on how to support abstract types has been added.
|
|
10
|
+
|
|
11
|
+
### Security
|
|
12
|
+
|
|
13
|
+
A remote execution attack vector has been fixed, which allowed executing arbitrary Ruby code within a CEL expression when calling functions on a variable declared as a CEL map. Example:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
env = Cel::Environment.new(declarations: { webhook: :map })
|
|
17
|
+
env.evaluate("webhook.payload.send('eval', 'File.write(\"test.txt\", \"Hello, world!\")')", webhook: { payload: {} })
|
|
18
|
+
```
|
|
19
|
+
|
|
3
20
|
## [0.4.0] - 2025-11-18
|
|
4
21
|
|
|
5
22
|
### Features
|
|
@@ -23,7 +40,7 @@ When the `tzinfo-data` gem is loaded, expressions will support human-friendly ti
|
|
|
23
40
|
|
|
24
41
|
### Bugfixes
|
|
25
42
|
|
|
26
|
-
* `has()` macro correctly works for
|
|
43
|
+
* `has()` macro correctly works for maps
|
|
27
44
|
|
|
28
45
|
## [0.3.0] - 2025-06-12
|
|
29
46
|
|
data/README.md
CHANGED
|
@@ -139,6 +139,29 @@ env2 = Cel::Environment.new(declarations: {foo: ->(a, b) { a + b }})
|
|
|
139
139
|
env2.evaluate("foo(2, 2)") #=> 4
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
### Abstract types
|
|
143
|
+
|
|
144
|
+
`cel-ruby` supports defining abstract types, via the `Cel::AbstractType` class.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class Tuple
|
|
148
|
+
attr_reader :value
|
|
149
|
+
def initialize(ary)
|
|
150
|
+
@value = ary
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
tuple_type = Class.new(Cel::AbstractType) do
|
|
155
|
+
def convert(value)
|
|
156
|
+
Tuple.new(value)
|
|
157
|
+
end
|
|
158
|
+
end.new(:tuple, Cel::TYPES[:int], Cel::TYPES[:int])
|
|
159
|
+
env = Cel::Environment.new(declarations: { tuple: Cel::Function(:int, :int, return_type: tuple_type) {|a, b| [a, b] } })
|
|
160
|
+
env.check('tuple(1, 2)') #=> tuple type
|
|
161
|
+
res = env.evaluate('tuple(1, 2)') #=> Tuple instance
|
|
162
|
+
res.value #=> [1, 2]
|
|
163
|
+
```
|
|
164
|
+
|
|
142
165
|
## Spec Coverage
|
|
143
166
|
|
|
144
167
|
`cel` is tested against the conformance suite from the [cel-spec repository](https://github.com/google/cel-spec/tree/master/conformance), and supports all features from the language except:
|
|
@@ -185,7 +208,7 @@ The parser is based on the grammar defined in [cel-spec](https://github.com/goog
|
|
|
185
208
|
Changes in the parser are therefore accomplished by modifying the `parser.ry` file and running:
|
|
186
209
|
|
|
187
210
|
```bash
|
|
188
|
-
> bundle exec racc -o lib/cel/parser.rb lib/cel/parser.ry
|
|
211
|
+
> bundle exec racc -F -o lib/cel/parser.rb lib/cel/parser.ry
|
|
189
212
|
```
|
|
190
213
|
|
|
191
214
|
## Contributing
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module CelMethods
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
super
|
|
7
|
+
klass.class_eval do
|
|
8
|
+
@cel_methods = [] # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
9
|
+
|
|
10
|
+
extend ClassMethods
|
|
11
|
+
include InstanceMethods
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
attr_reader :cel_methods
|
|
17
|
+
|
|
18
|
+
def define_cel_method(meth, &blk)
|
|
19
|
+
Ractor.make_shareable(blk) if defined?(Ractor)
|
|
20
|
+
define_method(meth, &blk)
|
|
21
|
+
|
|
22
|
+
@cel_methods << meth # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inherited(klass)
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
klass.instance_variable_set(:@cel_methods, [])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def freeze(*a)
|
|
32
|
+
super
|
|
33
|
+
@cel_methods.freeze(*a) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module InstanceMethods
|
|
38
|
+
def cel_send(meth, *args)
|
|
39
|
+
meth = meth.to_sym
|
|
40
|
+
raise NoCelMethodError unless cel_method_defined?(meth)
|
|
41
|
+
|
|
42
|
+
public_send(meth, *args)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cel_method_defined?(meth)
|
|
46
|
+
klass = self.class
|
|
47
|
+
|
|
48
|
+
while klass != Literal
|
|
49
|
+
return true if klass.cel_methods.include?(meth)
|
|
50
|
+
|
|
51
|
+
klass = klass.superclass
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
klass.cel_methods.include?(meth)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Bool < Literal
|
|
5
|
+
def initialize(value)
|
|
6
|
+
super(:bool, value)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
(LOGICAL_OPERATORS - %w[== != in]).each do |op|
|
|
10
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
11
|
+
define_cel_method(:#{op}) do |other|
|
|
12
|
+
return super(other) unless other.is_a?(Bool)
|
|
13
|
+
|
|
14
|
+
lhs = @value ? 1 : 0
|
|
15
|
+
rhs = other.value ? 1 : 0
|
|
16
|
+
lhs.__send__(__method__, rhs)
|
|
17
|
+
end
|
|
18
|
+
OUT
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def !
|
|
22
|
+
Bool.cast(super)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
TRUE_VALUE = new(true)
|
|
26
|
+
FALSE_VALUE = new(false)
|
|
27
|
+
|
|
28
|
+
def self.cast(val)
|
|
29
|
+
return val if val.is_a?(Bool)
|
|
30
|
+
|
|
31
|
+
val ? TRUE_VALUE : FALSE_VALUE
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Bytes < Literal
|
|
5
|
+
attr_reader :string
|
|
6
|
+
|
|
7
|
+
def initialize(value)
|
|
8
|
+
super(:bytes, value)
|
|
9
|
+
@string = value.pack("C*")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_ary
|
|
13
|
+
[self]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias_method :to_ruby_type, :string
|
|
17
|
+
|
|
18
|
+
(LOGICAL_OPERATORS - %w[==]).each do |op|
|
|
19
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
20
|
+
define_cel_method(:#{op}) do |other|
|
|
21
|
+
other.is_a?(Cel::Bytes) ? Bool.cast(@string.__send__(__method__, other.string)) : super(other)
|
|
22
|
+
end
|
|
23
|
+
OUT
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
%i[+ -].each do |op|
|
|
27
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
28
|
+
define_cel_method(:#{op}) do |other|
|
|
29
|
+
Bytes.new(@value + other.value)
|
|
30
|
+
end
|
|
31
|
+
OUT
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Condition
|
|
5
|
+
attr_reader :if, :then, :else, :depth
|
|
6
|
+
|
|
7
|
+
def initialize(if_, then_, else_, depth: 1)
|
|
8
|
+
@if = if_
|
|
9
|
+
@then = then_
|
|
10
|
+
@else = else_
|
|
11
|
+
@depth = depth
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
TYPES[:any]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ==(other)
|
|
19
|
+
other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Duration < Literal
|
|
5
|
+
def initialize(value)
|
|
6
|
+
value =
|
|
7
|
+
case value
|
|
8
|
+
when ::String
|
|
9
|
+
init_from_string(value)
|
|
10
|
+
when Hash
|
|
11
|
+
seconds, nanos = value.values_at(:seconds, :nanos)
|
|
12
|
+
seconds ||= 0
|
|
13
|
+
nanos ||= 0
|
|
14
|
+
seconds + (nanos / 1_000_000_000.0)
|
|
15
|
+
else
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
raise EvaluateError, "out of range" unless (value * 1_000_000_000).between?(-MAX_INT, MAX_INT)
|
|
20
|
+
|
|
21
|
+
super(:duration, value)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
seconds = getSeconds
|
|
26
|
+
millis = getMilliseconds
|
|
27
|
+
millis.positive? ? "#{seconds}s#{millis}m" : "#{seconds}s"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
define_cel_method(:+) do |other|
|
|
31
|
+
case other
|
|
32
|
+
when Cel::Duration
|
|
33
|
+
Cel::Duration.new(@value + other.value)
|
|
34
|
+
when Cel::Timestamp
|
|
35
|
+
Cel::Timestamp.new(other.value + @value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
define_cel_method(:-) do |other|
|
|
40
|
+
case other
|
|
41
|
+
when Cel::Duration
|
|
42
|
+
Cel::Duration.new(@value - other.value)
|
|
43
|
+
else
|
|
44
|
+
raise Error, "invalid operand #{other} for `-`"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
LOGICAL_OPERATORS.each do |op|
|
|
49
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
50
|
+
define_cel_method(:#{op}) do |other|
|
|
51
|
+
case other
|
|
52
|
+
when Cel::Literal
|
|
53
|
+
Bool.cast(super(other))
|
|
54
|
+
when Numeric
|
|
55
|
+
@value == other
|
|
56
|
+
|
|
57
|
+
else
|
|
58
|
+
super(other)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
OUT
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Cel Functions
|
|
65
|
+
|
|
66
|
+
define_cel_method(:getHours) do
|
|
67
|
+
Cel::Number.new(:int, (getMinutes / 60).to_i)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
define_cel_method(:getMinutes) do
|
|
71
|
+
Cel::Number.new(:int, (getSeconds / 60).to_i)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
define_cel_method(:getSeconds) do
|
|
75
|
+
Cel::Number.new(:int, @value.divmod(1).first)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
define_cel_method(:getMilliseconds) do
|
|
79
|
+
Cel::Number.new(:int, (@value.divmod(1).last * 1000).round)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def to_ruby_type
|
|
83
|
+
Protobuf.duration_class.new(seconds: getSeconds.value, nanos: getMilliseconds.value * 1_000_000_000.0)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def init_from_string(value)
|
|
89
|
+
seconds = 0
|
|
90
|
+
nanos = 0
|
|
91
|
+
value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
|
|
92
|
+
case units
|
|
93
|
+
when "h"
|
|
94
|
+
seconds += Cel.to_numeric(duration) * 60 * 60
|
|
95
|
+
when "m"
|
|
96
|
+
seconds += Cel.to_numeric(duration) * 60
|
|
97
|
+
when "s"
|
|
98
|
+
seconds += Cel.to_numeric(duration)
|
|
99
|
+
when "ms"
|
|
100
|
+
nanos += Cel.to_numeric(duration) * 1000 * 1000
|
|
101
|
+
when "us"
|
|
102
|
+
nanos += Cel.to_numeric(duration) * 1000
|
|
103
|
+
when "ns"
|
|
104
|
+
nanos += Cel.to_numeric(duration)
|
|
105
|
+
else
|
|
106
|
+
raise EvaluateError, "#{units} is unsupported"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
duration = seconds + (nanos / 1_000_000_000.0)
|
|
110
|
+
duration = -duration if value.start_with?("-")
|
|
111
|
+
duration
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Function
|
|
5
|
+
attr_reader :label, :types, :type
|
|
6
|
+
|
|
7
|
+
def initialize(*types, label: nil, return_type: nil, &func)
|
|
8
|
+
unless func.nil?
|
|
9
|
+
types = Array.new(func.arity) { TYPES[:any] } if types.empty?
|
|
10
|
+
raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
|
|
11
|
+
end
|
|
12
|
+
@types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
|
|
13
|
+
@type = if return_type.nil?
|
|
14
|
+
TYPES[:any]
|
|
15
|
+
else
|
|
16
|
+
return_type.is_a?(Type) ? return_type : TYPES[return_type]
|
|
17
|
+
end
|
|
18
|
+
@func = func
|
|
19
|
+
@label = label
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(*args)
|
|
23
|
+
result = @func.call(*args)
|
|
24
|
+
|
|
25
|
+
@type.convert(result)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
mod = self
|
|
30
|
+
mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
|
|
31
|
+
mod::Function.new(*args, **kwargs, &blk)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Group
|
|
5
|
+
attr_reader :value
|
|
6
|
+
|
|
7
|
+
def initialize(value)
|
|
8
|
+
@value = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def type
|
|
12
|
+
TYPES[:any]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
other.is_a?(Group) && @value == other.value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module Cel
|
|
6
|
+
class Identifier < SimpleDelegator
|
|
7
|
+
attr_reader :id
|
|
8
|
+
|
|
9
|
+
attr_accessor :type
|
|
10
|
+
|
|
11
|
+
def initialize(identifier, package = nil)
|
|
12
|
+
@id = identifier
|
|
13
|
+
@type = TYPES[:any]
|
|
14
|
+
@package = package
|
|
15
|
+
super(@id)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ==(other)
|
|
19
|
+
super || other.to_s == @id.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
@id.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
alias_method :to_ruby_type, :to_s
|
|
27
|
+
|
|
28
|
+
def try_convert_to_proto_type
|
|
29
|
+
Protobuf.convert_to_proto_type(@id.to_s, @package)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Invoke
|
|
5
|
+
attr_accessor :var
|
|
6
|
+
|
|
7
|
+
attr_reader :func, :args, :depth
|
|
8
|
+
|
|
9
|
+
def self.new(func:, var: nil, args: nil, **rest)
|
|
10
|
+
Protobuf.try_invoke_from(var, func, args) || super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type
|
|
14
|
+
TYPES[:any]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(func:, var: nil, args: nil, depth: 1, package: nil)
|
|
18
|
+
@var = var
|
|
19
|
+
@func = func.to_sym
|
|
20
|
+
@args = args
|
|
21
|
+
@package = package
|
|
22
|
+
@depth = depth
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ==(other)
|
|
26
|
+
case other
|
|
27
|
+
when Invoke
|
|
28
|
+
@var == other.var && @func == other.func && @args == other.args
|
|
29
|
+
when Array
|
|
30
|
+
[@var, @func, @args].compact == other
|
|
31
|
+
else
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
if var
|
|
38
|
+
if indexing?
|
|
39
|
+
"#{var}[#{args}]"
|
|
40
|
+
else
|
|
41
|
+
"#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
"#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def try_convert_to_proto_type
|
|
49
|
+
return unless @var
|
|
50
|
+
|
|
51
|
+
proto_type = Protobuf.convert_to_proto_type(@var.to_s, @package)
|
|
52
|
+
|
|
53
|
+
return unless proto_type
|
|
54
|
+
|
|
55
|
+
return proto_type unless proto_type.const_defined?(@func)
|
|
56
|
+
|
|
57
|
+
proto_type.const_get(@func)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def indexing?
|
|
61
|
+
@func == :[]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class List < Literal
|
|
5
|
+
attr_reader :depth
|
|
6
|
+
|
|
7
|
+
def initialize(value, depth: 1)
|
|
8
|
+
value = value.map do |v|
|
|
9
|
+
Literal.to_cel_type(v)
|
|
10
|
+
end
|
|
11
|
+
super(TYPES[:list], value)
|
|
12
|
+
@depth = depth
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
define_cel_method(:[]) do |key|
|
|
16
|
+
case key
|
|
17
|
+
when Number
|
|
18
|
+
if key.type == :double
|
|
19
|
+
val = key.value
|
|
20
|
+
|
|
21
|
+
raise InvalidArgumentError, key unless (val % 1).zero?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super(key)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_ary
|
|
29
|
+
[self]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_ruby_type
|
|
33
|
+
value.map(&:to_ruby_type)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
%i[+ -].each do |op|
|
|
37
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
38
|
+
define_cel_method(:#{op}) do |other|
|
|
39
|
+
List.new(@value.send(__method__, other.value))
|
|
40
|
+
end
|
|
41
|
+
OUT
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def by_max_depth
|
|
45
|
+
max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
|
|
46
|
+
|
|
47
|
+
# return the last value if all options have the same depth
|
|
48
|
+
return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
|
|
49
|
+
|
|
50
|
+
max
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
"[#{@value.map(&:to_s).join(", ")}]"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def calc_depth(element, acc)
|
|
60
|
+
case element
|
|
61
|
+
when List
|
|
62
|
+
element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
63
|
+
when Map
|
|
64
|
+
element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
65
|
+
else
|
|
66
|
+
acc
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Literal < SimpleDelegator
|
|
5
|
+
include CelMethods
|
|
6
|
+
|
|
7
|
+
attr_reader :type, :value
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def to_cel_type(val)
|
|
11
|
+
if val.is_a?(Protobuf.base_class) ||
|
|
12
|
+
val.is_a?(Struct) # no-protobuf mode
|
|
13
|
+
val = Protobuf.try_convert_from_wrapper(val)
|
|
14
|
+
|
|
15
|
+
return val if val.is_a?(Protobuf.base_class) # still
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
case val
|
|
19
|
+
when Literal, Identifier, Invoke, Operation, Condition, Group, # already cel
|
|
20
|
+
Protobuf.enum_class # already a usable class a la protobuf
|
|
21
|
+
val
|
|
22
|
+
when Protobuf.map_class
|
|
23
|
+
Map.new(val.to_h)
|
|
24
|
+
when ::String
|
|
25
|
+
String.new(val)
|
|
26
|
+
when ::Symbol
|
|
27
|
+
Identifier.new(val)
|
|
28
|
+
when ::Integer
|
|
29
|
+
Number.new(:int, val)
|
|
30
|
+
when ::Float, ::BigDecimal
|
|
31
|
+
Number.new(:double, val)
|
|
32
|
+
when ::Hash
|
|
33
|
+
Map.new(val)
|
|
34
|
+
when ::Array
|
|
35
|
+
List.new(val)
|
|
36
|
+
when true, false
|
|
37
|
+
Bool.cast(val)
|
|
38
|
+
when nil
|
|
39
|
+
Null::INSTANCE
|
|
40
|
+
when Time
|
|
41
|
+
Timestamp.new(val)
|
|
42
|
+
else
|
|
43
|
+
raise BindingError, "can't convert #{val} to CEL type"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(type, value)
|
|
49
|
+
@type = type.is_a?(Type) ? type : TYPES[type]
|
|
50
|
+
@value = value
|
|
51
|
+
super(value)
|
|
52
|
+
check
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
define_cel_method(:==) do |other|
|
|
56
|
+
case other
|
|
57
|
+
when Literal
|
|
58
|
+
@type == other.type && @value == other.value
|
|
59
|
+
else
|
|
60
|
+
@value == other
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
define_cel_method(:"!=") do |other|
|
|
65
|
+
!cel_send(:==, other)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def check; end
|
|
71
|
+
|
|
72
|
+
alias_method :to_ruby_type, :value
|
|
73
|
+
end
|
|
74
|
+
end
|