cel 0.1.2 → 0.2.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 +52 -0
- data/README.md +13 -1
- data/lib/cel/ast/elements/protobuf.rb +127 -0
- data/lib/cel/ast/elements.rb +246 -4
- data/lib/cel/ast/types.rb +7 -1
- data/lib/cel/checker.rb +113 -35
- data/lib/cel/context.rb +8 -3
- data/lib/cel/encoder.rb +102 -0
- data/lib/cel/environment.rb +19 -6
- data/lib/cel/parser.rb +28 -12
- data/lib/cel/program.rb +15 -3
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +1 -6
- metadata +18 -3
- data/lib/cel/protobuf.rb +0 -175
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 868e7e643aad1444aa54da0dab45e583a49abf2e6af0dcbbd1512b88af209e88
|
4
|
+
data.tar.gz: 2f174f88813b63181f1c16e701d1baf961eda0d9ef18c575b29b5afdc53d450c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26c83e074c1729c00f5b8c3ca271865bb009f562326c6b6ddca3d5c127cf7c7f33ab93b3c964d4db0eafbe442e07dc1413abf2dc5b5ac487950da2d02fc8cd69
|
7
|
+
data.tar.gz: 3aa55062c1693eb5c3d3ad45bb09d95028c5115a9428d8bf2fdd3189b5c8869c4b6d92c6aa3e864b515f555fe78d3015fbeaf03799d64f0ef9f8365ce95a87d0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,57 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2023-01-17
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
#### Timestamp/Duration types
|
8
|
+
|
9
|
+
Implementing `Cel::Timestamp` and `Cel::Duration` CEL types, along with support for functions for those types (such as `getDate`, `getDayOfYear`).
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Cel::Environment.new.evaluate("timestamp('2022-12-25T00:00:00Z').getDate()") #=> 25
|
13
|
+
Cel::Environment.new.evaluate("duration('3600s10ms').getHours()") #=> 1
|
14
|
+
```
|
15
|
+
|
16
|
+
#### Protobuf-to-CEL conversion
|
17
|
+
|
18
|
+
Support for auto-conversion of certain protobuf messages in the `google.protobf` package to CEL types.
|
19
|
+
|
20
|
+
https://github.com/google/cel-spec/blob/master/doc/langdef.md#dynamic-values
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
Cel::Environment.new.evaluate("google.protobuf.BoolValue{value: true}") #=> true
|
24
|
+
Cel::Environment.new.evaluate("google.protobuf.Value{string_value: 'bla'}") #=> "bla"
|
25
|
+
```
|
26
|
+
|
27
|
+
#### Custom Functions
|
28
|
+
|
29
|
+
`cel` supports the definition of custom functions in the environment, to be used in expressions:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Cel::Environment.new(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b }).evaluate("foo(2, 2)") #=> 4
|
33
|
+
```
|
34
|
+
|
35
|
+
### Expression encoding/decoding
|
36
|
+
|
37
|
+
Expressions can now be encoded and decoded, to improve storage / reparsing to and from JSON, for example.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
enc = Cel::Environment.new.encode("1 == 2") #=> ["op", ...
|
41
|
+
store_to_db(JSON.dump(enc))
|
42
|
+
|
43
|
+
# then, somewhere else
|
44
|
+
env = Cel::Environment.new
|
45
|
+
ast = env.decode(JSON.parse(read_from_db))
|
46
|
+
env.evaluate(ast) #=> 3
|
47
|
+
```
|
48
|
+
|
49
|
+
**NOTE**: This feature is only available in ruby 3.1 .
|
50
|
+
|
51
|
+
### Bugfixes
|
52
|
+
|
53
|
+
* fixed parser bug disallowing identifiers composed "true" or "false" (such as "true_name").
|
54
|
+
|
3
55
|
## [0.1.2] - 2022-11-10
|
4
56
|
|
5
57
|
point release to update links in rubygems.
|
data/README.md
CHANGED
@@ -71,7 +71,7 @@ puts return_value #=> true
|
|
71
71
|
If `google/protobuf` is available in the environment, `cel-ruby` will also be able to integrate with protobuf declarations in CEL expressions.
|
72
72
|
|
73
73
|
```ruby
|
74
|
-
|
74
|
+
require "google/protobuf"
|
75
75
|
require "cel"
|
76
76
|
|
77
77
|
env = Cel::Environment.new
|
@@ -79,6 +79,18 @@ env = Cel::Environment.new
|
|
79
79
|
env.evaluate("google.protobuf.Duration{seconds: 123}.seconds == 123") #=> true
|
80
80
|
```
|
81
81
|
|
82
|
+
### Custom functions
|
83
|
+
|
84
|
+
`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`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
env = environment(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b})
|
88
|
+
env.evaluate("foo(2, 2)") #=> 4
|
89
|
+
|
90
|
+
# this is also possible, just not as type-safe
|
91
|
+
env2 = environment(foo: -> (a, b) { a + b})
|
92
|
+
env2.evaluate("foo(2, 2)") #=> 4
|
93
|
+
```
|
82
94
|
|
83
95
|
## Supported Rubies
|
84
96
|
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "google/protobuf/struct_pb"
|
4
|
+
require "google/protobuf/wrappers_pb"
|
5
|
+
require "google/protobuf/any_pb"
|
6
|
+
require "google/protobuf/well_known_types"
|
7
|
+
|
8
|
+
module Cel
|
9
|
+
module Protobuf
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def convert_from_protobuf(msg)
|
13
|
+
case msg
|
14
|
+
when Google::Protobuf::Any
|
15
|
+
|
16
|
+
any.unpack(type_msg).to_ruby
|
17
|
+
when Google::Protobuf::ListValue
|
18
|
+
msg.to_a
|
19
|
+
when Google::Protobuf::Struct
|
20
|
+
msg.to_h
|
21
|
+
when Google::Protobuf::Value
|
22
|
+
msg.to_ruby
|
23
|
+
when Google::Protobuf::BoolValue,
|
24
|
+
Google::Protobuf::BytesValue,
|
25
|
+
Google::Protobuf::DoubleValue,
|
26
|
+
Google::Protobuf::FloatValue,
|
27
|
+
Google::Protobuf::Int32Value,
|
28
|
+
Google::Protobuf::Int64Value,
|
29
|
+
Google::Protobuf::UInt32Value,
|
30
|
+
Google::Protobuf::UInt64Value,
|
31
|
+
Google::Protobuf::NullValue,
|
32
|
+
Google::Protobuf::StringValue
|
33
|
+
msg.value
|
34
|
+
when Google::Protobuf::Timestamp
|
35
|
+
msg.to_time
|
36
|
+
when Google::Protobuf::Duration
|
37
|
+
Cel::Duration.new(seconds: msg.seconds, nanos: msg.nanos)
|
38
|
+
else
|
39
|
+
raise Error, "#{msg.class}: protobuf to cel unsupported"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_from_type(type, value)
|
44
|
+
case type
|
45
|
+
when "Any", "google.protobuf.Any"
|
46
|
+
type_url = value[Identifier.new("type_url")].value
|
47
|
+
_, type_msg = type_url.split("/", 2)
|
48
|
+
type_msg = const_get(type_msg.split(".").map(&:capitalize).join("::"))
|
49
|
+
encoded_msg = value[Identifier.new("value")].value.pack("C*")
|
50
|
+
any = Google::Protobuf::Any.new(type_url: type_url, value: encoded_msg)
|
51
|
+
value = Literal.to_cel_type(any.unpack(type_msg).to_ruby)
|
52
|
+
when "ListValue", "google.protobuf.ListValue"
|
53
|
+
value = value.nil? ? List.new([]) : value[Identifier.new("values")]
|
54
|
+
when "Struct", "google.protobuf.Struct"
|
55
|
+
value = value.nil? ? Map.new({}) : value[Identifier.new("fields")]
|
56
|
+
when "Value", "google.protobuf.Value"
|
57
|
+
return Null.new if value.nil?
|
58
|
+
|
59
|
+
key = value.keys.first
|
60
|
+
|
61
|
+
value = value.fetch(key, value)
|
62
|
+
|
63
|
+
value = Number.new(:double, value) if key == "number_value"
|
64
|
+
|
65
|
+
value
|
66
|
+
when "BoolValue", "google.protobuf.BoolValue"
|
67
|
+
value = value.nil? ? Bool.new(false) : value[Identifier.new("value")]
|
68
|
+
when "BytesValue", "google.protobuf.BytesValue"
|
69
|
+
value = value[Identifier.new("value")]
|
70
|
+
when "DoubleValue", "google.protobuf.DoubleValue",
|
71
|
+
"FloatValue", "google.protobuf.FloatValue"
|
72
|
+
value = value.nil? ? Number.new(:double, 0.0) : value[Identifier.new("value")]
|
73
|
+
value.type = TYPES[:double]
|
74
|
+
when "Int32Value", "google.protobuf.Int32Value",
|
75
|
+
"Int64Value", "google.protobuf.Int64Value"
|
76
|
+
value = value.nil? ? Number.new(:int, 0) : value[Identifier.new("value")]
|
77
|
+
when "Uint32Value", "google.protobuf.UInt32Value",
|
78
|
+
"Uint64Value", "google.protobuf.UInt64Value"
|
79
|
+
value = value.nil? ? Number.new(:uint, 0) : value[Identifier.new("value")]
|
80
|
+
when "NullValue", "google.protobuf.NullValue"
|
81
|
+
value = Null.new
|
82
|
+
when "StringValue", "google.protobuf.StringValue"
|
83
|
+
value = value.nil? ? String.new(+"") : value[Identifier.new("value")]
|
84
|
+
when "Timestamp", "google.protobuf.Timestamp"
|
85
|
+
seconds = value.fetch(Identifier.new("seconds"), 0)
|
86
|
+
nanos = value.fetch(Identifier.new("nanos"), 0)
|
87
|
+
value = Timestamp.new(Time.at(seconds, nanos, :nanosecond))
|
88
|
+
when "Duration", "google.protobuf.Duration"
|
89
|
+
seconds = value.fetch(Identifier.new("seconds"), 0)
|
90
|
+
nanos = value.fetch(Identifier.new("nanos"), 0)
|
91
|
+
value = Duration.new(seconds: seconds, nanos: nanos)
|
92
|
+
end
|
93
|
+
value
|
94
|
+
end
|
95
|
+
|
96
|
+
def try_invoke_from(var, func, args)
|
97
|
+
case var
|
98
|
+
when "Any", "google.protobuf.Any",
|
99
|
+
"ListValue", "google.protobuf.ListValue",
|
100
|
+
"Struct", "google.protobuf.Struct",
|
101
|
+
"Value", "google.protobuf.Value",
|
102
|
+
"BoolValue", "google.protobuf.BoolValue",
|
103
|
+
"BytesValue", "google.protobuf.BytesValue",
|
104
|
+
"DoubleValue", "google.protobuf.DoubleValue",
|
105
|
+
"FloatValue", "google.protobuf.FloatValue",
|
106
|
+
"Int32Value", "google.protobuf.Int32Value",
|
107
|
+
"Int64Value", "google.protobuf.Int64Value",
|
108
|
+
"Uint32Value", "google.protobuf.Uint32Value",
|
109
|
+
"Uint64Value", "google.protobuf.Uint64Value",
|
110
|
+
"NullValue", "google.protobuf.NullValue",
|
111
|
+
"StringValue", "google.protobuf.StringValue",
|
112
|
+
"Timestamp", "google.protobuf.Timestamp",
|
113
|
+
"Duration", "google.protobuf.Duration"
|
114
|
+
protoclass = var.split(".").last
|
115
|
+
protoclass = Google::Protobuf.const_get(protoclass)
|
116
|
+
|
117
|
+
value = if args.nil? && protoclass.constants.include?(func.to_sym)
|
118
|
+
protoclass.const_get(func)
|
119
|
+
else
|
120
|
+
protoclass.__send__(func, *args)
|
121
|
+
end
|
122
|
+
|
123
|
+
Literal.to_cel_type(value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/cel/ast/elements.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "time"
|
3
4
|
require "delegate"
|
5
|
+
require_relative "elements/protobuf"
|
4
6
|
|
5
7
|
module Cel
|
6
8
|
LOGICAL_OPERATORS = %w[< <= >= > == != in].freeze
|
@@ -20,11 +22,22 @@ module Cel
|
|
20
22
|
def ==(other)
|
21
23
|
super || other.to_s == @id.to_s
|
22
24
|
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@id.to_s
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
31
|
class Message < SimpleDelegator
|
26
32
|
attr_reader :type, :struct
|
27
33
|
|
34
|
+
def self.new(type, struct)
|
35
|
+
value = convert_from_type(type, struct)
|
36
|
+
return value if value.is_a?(Null) || value != struct
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
28
41
|
def initialize(type, struct)
|
29
42
|
@struct = Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values)
|
30
43
|
@type = type.is_a?(Type) ? type : MapType.new(struct.to_h do |k, v|
|
@@ -36,11 +49,32 @@ module Cel
|
|
36
49
|
def field?(key)
|
37
50
|
!@type.get(key).nil?
|
38
51
|
end
|
52
|
+
|
53
|
+
def self.convert_from_type(type, value)
|
54
|
+
case type
|
55
|
+
when Invoke, Identifier
|
56
|
+
spread_type = type.to_s
|
57
|
+
Protobuf.convert_from_type(spread_type, value)
|
58
|
+
when Type
|
59
|
+
[type, value]
|
60
|
+
else
|
61
|
+
[
|
62
|
+
MapType.new(struct.to_h do |k, v|
|
63
|
+
[Literal.to_cel_type(k), Literal.to_cel_type(v)]
|
64
|
+
end),
|
65
|
+
Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values),
|
66
|
+
]
|
67
|
+
end
|
68
|
+
end
|
39
69
|
end
|
40
70
|
|
41
71
|
class Invoke
|
42
72
|
attr_reader :var, :func, :args
|
43
73
|
|
74
|
+
def self.new(func:, var: nil, args: nil)
|
75
|
+
Protobuf.try_invoke_from(var, func, args) || super
|
76
|
+
end
|
77
|
+
|
44
78
|
def initialize(func:, var: nil, args: nil)
|
45
79
|
@var = var
|
46
80
|
@func = func.to_sym
|
@@ -48,10 +82,14 @@ module Cel
|
|
48
82
|
end
|
49
83
|
|
50
84
|
def ==(other)
|
51
|
-
|
52
|
-
|
85
|
+
case other
|
86
|
+
when Invoke
|
87
|
+
@var == other.var && @func == other.func && @args == other.args
|
88
|
+
when Array
|
53
89
|
[@var, @func, @args].compact == other
|
54
|
-
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
55
93
|
end
|
56
94
|
|
57
95
|
def to_s
|
@@ -67,6 +105,33 @@ module Cel
|
|
67
105
|
end
|
68
106
|
end
|
69
107
|
|
108
|
+
class Function
|
109
|
+
attr_reader :types, :type
|
110
|
+
|
111
|
+
def initialize(*types, return_type: nil, &func)
|
112
|
+
unless func.nil?
|
113
|
+
types = Array.new(func.arity) { TYPES[:any] } if types.empty?
|
114
|
+
raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
|
115
|
+
end
|
116
|
+
@types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
|
117
|
+
@type = if return_type.nil?
|
118
|
+
TYPES[:any]
|
119
|
+
else
|
120
|
+
return_type.is_a?(Type) ? return_type : TYPES[return_type]
|
121
|
+
end
|
122
|
+
@func = func
|
123
|
+
end
|
124
|
+
|
125
|
+
def call(*args)
|
126
|
+
Literal.to_cel_type(@func.call(*args))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
mod = self
|
131
|
+
mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
|
132
|
+
mod::Function.new(*args, **kwargs, &blk)
|
133
|
+
end
|
134
|
+
|
70
135
|
class Literal < SimpleDelegator
|
71
136
|
attr_reader :type, :value
|
72
137
|
|
@@ -82,6 +147,8 @@ module Cel
|
|
82
147
|
end
|
83
148
|
|
84
149
|
def self.to_cel_type(val)
|
150
|
+
val = Protobuf.convert_from_protobuf(val) if val.is_a?(Google::Protobuf::MessageExts)
|
151
|
+
|
85
152
|
case val
|
86
153
|
when Literal, Identifier
|
87
154
|
val
|
@@ -102,6 +169,8 @@ module Cel
|
|
102
169
|
Bool.new(val)
|
103
170
|
when nil
|
104
171
|
Null.new
|
172
|
+
when Time
|
173
|
+
Timestamp.new(val)
|
105
174
|
else
|
106
175
|
raise BindingError, "can't convert #{val} to CEL type"
|
107
176
|
end
|
@@ -274,12 +343,178 @@ module Cel
|
|
274
343
|
end
|
275
344
|
end
|
276
345
|
|
346
|
+
class Timestamp < Literal
|
347
|
+
def initialize(value)
|
348
|
+
value = case value
|
349
|
+
when String then Time.parse(value)
|
350
|
+
when Numeric then Time.at(value)
|
351
|
+
else value
|
352
|
+
end
|
353
|
+
super(:timestamp, value)
|
354
|
+
end
|
355
|
+
|
356
|
+
def +(other)
|
357
|
+
Timestamp.new(@value + other.to_f)
|
358
|
+
end
|
359
|
+
|
360
|
+
def -(other)
|
361
|
+
case other
|
362
|
+
when Timestamp
|
363
|
+
Duration.new(@value - other.value)
|
364
|
+
when Duration
|
365
|
+
Timestamp.new(@value - other.to_f)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
LOGICAL_OPERATORS.each do |op|
|
370
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
371
|
+
def #{op}(other)
|
372
|
+
other.is_a?(Cel::Literal) ? Bool.new(super) : super
|
373
|
+
end
|
374
|
+
OUT
|
375
|
+
end
|
376
|
+
|
377
|
+
# Cel Functions
|
378
|
+
|
379
|
+
def getDate(tz = nil)
|
380
|
+
to_local_time(tz).day
|
381
|
+
end
|
382
|
+
|
383
|
+
def getDayOfMonth(tz = nil)
|
384
|
+
getDate(tz) - 1
|
385
|
+
end
|
386
|
+
|
387
|
+
def getDayOfWeek(tz = nil)
|
388
|
+
to_local_time(tz).wday
|
389
|
+
end
|
390
|
+
|
391
|
+
def getDayOfYear(tz = nil)
|
392
|
+
to_local_time(tz).yday - 1
|
393
|
+
end
|
394
|
+
|
395
|
+
def getMonth(tz = nil)
|
396
|
+
to_local_time(tz).month - 1
|
397
|
+
end
|
398
|
+
|
399
|
+
def getFullYear(tz = nil)
|
400
|
+
to_local_time(tz).year
|
401
|
+
end
|
402
|
+
|
403
|
+
def getHours(tz = nil)
|
404
|
+
to_local_time(tz).hour
|
405
|
+
end
|
406
|
+
|
407
|
+
def getMinutes(tz = nil)
|
408
|
+
to_local_time(tz).min
|
409
|
+
end
|
410
|
+
|
411
|
+
def getSeconds(tz = nil)
|
412
|
+
to_local_time(tz).sec
|
413
|
+
end
|
414
|
+
|
415
|
+
def getMilliseconds(tz = nil)
|
416
|
+
to_local_time(tz).nsec / 1_000_000
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
def to_local_time(tz = nil)
|
422
|
+
time = @value
|
423
|
+
if tz
|
424
|
+
tz = TZInfo::Timezone.get(tz) unless tz.match?(/\A[+-]\d{2,}:\d{2,}\z/)
|
425
|
+
time = time.getlocal(tz)
|
426
|
+
end
|
427
|
+
time
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
class Duration < Literal
|
432
|
+
def initialize(value)
|
433
|
+
value = case value
|
434
|
+
when String
|
435
|
+
init_from_string(value)
|
436
|
+
when Hash
|
437
|
+
seconds, nanos = value.values_at(:seconds, :nanos)
|
438
|
+
seconds ||= 0
|
439
|
+
nanos ||= 0
|
440
|
+
seconds + (nanos / 1_000_000_000.0)
|
441
|
+
else
|
442
|
+
value
|
443
|
+
end
|
444
|
+
super(:duration, value)
|
445
|
+
end
|
446
|
+
|
447
|
+
LOGICAL_OPERATORS.each do |op|
|
448
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
449
|
+
def #{op}(other)
|
450
|
+
case other
|
451
|
+
when Cel::Literal
|
452
|
+
Bool.new(super)
|
453
|
+
when Numeric
|
454
|
+
@value == other
|
455
|
+
|
456
|
+
else
|
457
|
+
super
|
458
|
+
end
|
459
|
+
end
|
460
|
+
OUT
|
461
|
+
end
|
462
|
+
|
463
|
+
# Cel Functions
|
464
|
+
|
465
|
+
def getHours
|
466
|
+
(getMinutes / 60).to_i
|
467
|
+
end
|
468
|
+
|
469
|
+
def getMinutes
|
470
|
+
(getSeconds / 60).to_i
|
471
|
+
end
|
472
|
+
|
473
|
+
def getSeconds
|
474
|
+
@value.divmod(1).first
|
475
|
+
end
|
476
|
+
|
477
|
+
def getMilliseconds
|
478
|
+
(@value.divmod(1).last * 1000).round
|
479
|
+
end
|
480
|
+
|
481
|
+
private
|
482
|
+
|
483
|
+
def init_from_string(value)
|
484
|
+
seconds = 0
|
485
|
+
nanos = 0
|
486
|
+
value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
|
487
|
+
case units
|
488
|
+
when "h"
|
489
|
+
seconds += Cel.to_numeric(duration) * 60 * 60
|
490
|
+
when "m"
|
491
|
+
seconds += Cel.to_numeric(duration) * 60
|
492
|
+
when "s"
|
493
|
+
seconds += Cel.to_numeric(duration)
|
494
|
+
when "ms"
|
495
|
+
nanos += Cel.to_numeric(duration) * 1000 * 1000
|
496
|
+
when "us"
|
497
|
+
nanos += Cel.to_numeric(duration) * 1000
|
498
|
+
when "ns"
|
499
|
+
nanos += Cel.to_numeric(duration)
|
500
|
+
else
|
501
|
+
raise EvaluateError, "#{units} is unsupported"
|
502
|
+
end
|
503
|
+
end
|
504
|
+
seconds + (nanos / 1_000_000_000.0)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
277
508
|
class Group
|
278
509
|
attr_reader :value
|
279
510
|
|
280
511
|
def initialize(value)
|
281
512
|
@value = value
|
282
513
|
end
|
514
|
+
|
515
|
+
def ==(other)
|
516
|
+
other.is_a?(Group) && @value == other.value
|
517
|
+
end
|
283
518
|
end
|
284
519
|
|
285
520
|
class Operation
|
@@ -294,10 +529,13 @@ module Cel
|
|
294
529
|
end
|
295
530
|
|
296
531
|
def ==(other)
|
297
|
-
|
532
|
+
case other
|
533
|
+
when Array
|
298
534
|
other.size == @operands.size + 1 &&
|
299
535
|
other.first == @op &&
|
300
536
|
other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
|
537
|
+
when Operation
|
538
|
+
@op == other.op && @type == other.type && @operands == other.operands
|
301
539
|
else
|
302
540
|
super
|
303
541
|
end
|
@@ -318,5 +556,9 @@ module Cel
|
|
318
556
|
@then = then_
|
319
557
|
@else = else_
|
320
558
|
end
|
559
|
+
|
560
|
+
def ==(other)
|
561
|
+
other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
|
562
|
+
end
|
321
563
|
end
|
322
564
|
end
|
data/lib/cel/ast/types.rb
CHANGED
@@ -14,6 +14,8 @@ module Cel
|
|
14
14
|
@type.to_s
|
15
15
|
end
|
16
16
|
|
17
|
+
alias_method :to_s, :to_str
|
18
|
+
|
17
19
|
def type
|
18
20
|
TYPES[:type]
|
19
21
|
end
|
@@ -32,6 +34,10 @@ module Cel
|
|
32
34
|
Bytes.new(value.bytes)
|
33
35
|
when :bool
|
34
36
|
Bool.new(value)
|
37
|
+
when :timestamp
|
38
|
+
Timestamp.new(value)
|
39
|
+
when :duration
|
40
|
+
Duration.new(value)
|
35
41
|
when :any
|
36
42
|
value
|
37
43
|
else
|
@@ -71,7 +77,7 @@ module Cel
|
|
71
77
|
|
72
78
|
# Primitive Cel Types
|
73
79
|
|
74
|
-
PRIMITIVE_TYPES = %i[int uint double bool string bytes list map null_type type].freeze
|
80
|
+
PRIMITIVE_TYPES = %i[int uint double bool string bytes list map timestamp duration null_type type].freeze
|
75
81
|
TYPES = PRIMITIVE_TYPES.to_h { |typ| [typ, Type.new(typ)] }
|
76
82
|
TYPES[:type] = Type.new(:type)
|
77
83
|
TYPES[:any] = Type.new(:any)
|