cel 0.2.3 → 0.3.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.
data/lib/cel/ast/types.rb CHANGED
@@ -7,11 +7,22 @@ module Cel
7
7
  end
8
8
 
9
9
  def ==(other)
10
- other == @type || super
10
+ (other.is_a?(Symbol) && other == @type) || super
11
11
  end
12
12
 
13
13
  def to_str
14
- @type.to_s
14
+ case @type
15
+ when :timestamp
16
+ "google.protobuf.Timestamp"
17
+ when :duration
18
+ "google.protobuf.Duration"
19
+ else
20
+ @type.to_s
21
+ end
22
+ end
23
+
24
+ def to_sym
25
+ @type
15
26
  end
16
27
 
17
28
  alias_method :to_s, :to_str
@@ -21,25 +32,122 @@ module Cel
21
32
  end
22
33
 
23
34
  def cast(value)
24
- value = value.value if value.is_a?(Literal)
35
+ type = nil
36
+ type = value.type.to_sym if value.is_a?(Literal)
25
37
 
26
38
  case @type
27
39
  when :int
28
- Number.new(:int, Integer(value))
40
+ case type
41
+ when :int, :uint, :string
42
+ value = Integer(value.value)
43
+
44
+ raise "Type conversion range error" unless (-(MAX_INT - 1)...MAX_INT).cover?(value)
45
+
46
+ Number.new(:int, value)
47
+ when :double
48
+ value = value.value
49
+
50
+ raise "Type conversion range error" unless (-(MAX_FLOAT - 1)...MAX_FLOAT).cover?(value)
51
+
52
+ Number.new(:int, Integer(value))
53
+ when :timestamp
54
+ Number.new(:int, value.value.to_i)
55
+ else
56
+ raise EvaluateError, "Type conversion error"
57
+ end
29
58
  when :uint
30
- Number.new(:uint, Integer(value).abs)
59
+ case type
60
+ when :int, :uint, :string
61
+ value = Integer(value.value)
62
+
63
+ raise EvaluateError, "Type conversion range error" unless (0...MAX_INT).cover?(value)
64
+
65
+ Number.new(:uint, value)
66
+ when :double
67
+ value = value.value
68
+
69
+ raise EvaluateError, "Type conversion range error" unless (0...MAX_FLOAT).cover?(value)
70
+
71
+ Number.new(:uint, Integer(value))
72
+ else
73
+ raise EvaluateError, "Type conversion error"
74
+ end
31
75
  when :double
32
- Number.new(:double, Float(value))
76
+ case type
77
+ when :double, :int, :uint
78
+ Number.new(:double, Float(value.value))
79
+ when :string
80
+ case value
81
+ when "NaN"
82
+ raise Error, "unsupported cast operation to #{@type}" unless defined?(Float::NAN)
83
+
84
+ Number.new(:double, Float::NAN)
85
+ else
86
+ Number.new(:double, Float(value.value))
87
+ end
88
+ else
89
+ raise EvaluateError, "Type conversion error"
90
+ end
33
91
  when :string
34
- String.new(String(value))
92
+ case type
93
+ when :bytes
94
+ value = value.string.force_encoding(Encoding::UTF_8)
95
+
96
+ raise EvaluateError, "Type conversion invalid UTF-8" unless value.valid_encoding?
97
+
98
+ String.new(value)
99
+ when :timestamp
100
+ String.new(value.value.iso8601(9).sub(/\.0+Z/, "Z"))
101
+ when :duration
102
+ String.new(value.to_s)
103
+ else
104
+ String.new(String(value.value))
105
+ end
35
106
  when :bytes
36
- Bytes.new(value.bytes)
107
+ case type
108
+ when :bytes
109
+ value
110
+ when :string
111
+ Bytes.new(value.value.bytes)
112
+ else
113
+ raise EvaluateError, "Type conversion error"
114
+ end
37
115
  when :bool
38
- Bool.new(value)
116
+ case type
117
+ when :bool
118
+ value
119
+ when :string
120
+ case value.value
121
+ when "1", "t", "true", "TRUE", "True"
122
+ Bool.new(true)
123
+ when "0", "f", "false", "FALSE", "False"
124
+ Bool.new(false)
125
+ else
126
+ raise EvaluateError, "Type conversion error"
127
+ end
128
+ else
129
+ raise EvaluateError, "Type conversion error"
130
+ end
131
+ when :null_type
132
+ Null::INSTANCE
39
133
  when :timestamp
40
- Timestamp.new(value)
134
+ case type
135
+ when :timestamp
136
+ value
137
+ when :int, :string
138
+ Timestamp.new(value.value)
139
+ else
140
+ raise EvaluateError, "Type conversion error"
141
+ end
41
142
  when :duration
42
- Duration.new(value)
143
+ case type
144
+ when :duration
145
+ value
146
+ when :string
147
+ Duration.new(value.value)
148
+ else
149
+ raise EvaluateError, "Type conversion error"
150
+ end
43
151
  when :any
44
152
  value
45
153
  else
@@ -58,11 +166,13 @@ module Cel
58
166
  end
59
167
 
60
168
  def get(idx)
169
+ return @element_type if @type_list.empty?
170
+
61
171
  @type_list[idx]
62
172
  end
63
173
 
64
174
  def ==(other)
65
- other == :list || super
175
+ (other.is_a?(self.class) && other.element_type == @element_type) || super
66
176
  end
67
177
 
68
178
  def cast(value)
@@ -73,18 +183,22 @@ module Cel
73
183
  class MapType < Type
74
184
  attr_accessor :element_type
75
185
 
186
+ attr_reader :type_map
187
+ protected :type_map
188
+
76
189
  def initialize(type_map)
77
190
  super(:map)
78
191
  @type_map = type_map
79
- @element_type = @type_map.empty? ? TYPES[:any] : @type_map.keys.sample.type
192
+ @element_type = @type_map.empty? ? TYPES[:any] : @type_map.sample.first.type
80
193
  end
81
194
 
82
195
  def get(attrib)
83
- _, value = @type_map.find { |k, _| k == attrib.to_s }
196
+ _, value = @type_map.find { |k, _| k == attrib }
84
197
  value
85
198
  end
86
199
 
87
200
  def ==(other)
201
+ (other.is_a?(self.class) && other.element_type == @element_type && other.type_map == @type_map) || super
88
202
  other == :map || super
89
203
  end
90
204
 
@@ -93,6 +207,12 @@ module Cel
93
207
  end
94
208
  end
95
209
 
210
+ class AbstractType < Type
211
+ def initialize(name, *params)
212
+ super(name)
213
+ @params = params
214
+ end
215
+ end
96
216
  # Primitive Cel Types
97
217
 
98
218
  PRIMITIVE_TYPES = %i[int uint double bool string bytes list map timestamp duration null_type type].freeze
@@ -118,10 +238,26 @@ module Cel
118
238
  MapType.new({})
119
239
  end
120
240
 
121
- type.element_type = super(*elem_type)
241
+ type.element_type = elem_type.is_a?(Type) || (elem_type.is_a?(Class) && elem_type < Protobuf.base_class) ?
242
+ elem_type :
243
+ super(*elem_type)
122
244
  type
123
245
  end
124
246
  end
125
247
 
126
248
  TYPES.singleton_class.prepend(CollectionTypeFetch)
249
+
250
+ # override == to ensure that cel timestamp/duration types
251
+ # compare positively with the protobuf counterparts.
252
+ timestamp_class = TYPES[:timestamp]
253
+
254
+ def timestamp_class.==(other)
255
+ other == Protobuf.timestamp_class || super
256
+ end
257
+
258
+ duration_class = TYPES[:duration]
259
+
260
+ def duration_class.==(other)
261
+ other == Protobuf.duration_class || super
262
+ end
127
263
  end
data/lib/cel/checker.rb CHANGED
@@ -2,11 +2,35 @@
2
2
 
3
3
  module Cel
4
4
  class Checker
5
- def initialize(declarations)
5
+ LOGICAL_EXPECTED_TYPES = %i[bool int uint double string bytes timestamp duration].freeze
6
+ ADD_EXPECTED_TYPES = %i[int uint double string bytes list duration].freeze
7
+ SUB_EXPECTED_TYPES = %i[int uint double duration].freeze
8
+ MULTIDIV_EXPECTED_TYPES = %i[int uint double].freeze
9
+ REMAINDER_EXPECTED_TYPES = %i[int uint].freeze
10
+
11
+ BOOLABLE_OPERATORS = %w[&& || == != < <= >= >].freeze
12
+
13
+ CAST_ALLOWED_TYPES = {
14
+ int: %i[int uint double string timestamp], # TODO: enum
15
+ uint: %i[int uint double string],
16
+ string: %i[int uint double bytes bool bytes string timestamp duration],
17
+ double: %i[double int uint string],
18
+ bytes: %i[bytes string],
19
+ bool: %i[bool string],
20
+ duration: %i[string duration],
21
+ timestamp: %i[int string timestamp],
22
+ }.freeze
23
+
24
+ attr_reader :declarations
25
+
26
+ def initialize(environment, declarations = environment.declarations)
27
+ @environment = environment
6
28
  @declarations = declarations
7
29
  end
8
30
 
9
31
  def check(ast)
32
+ return ast if ast.is_a?(Class) && ast < Protobuf.base_class
33
+
10
34
  case ast
11
35
  when Group
12
36
  check(ast.value)
@@ -15,7 +39,9 @@ module Cel
15
39
  when Operation
16
40
  check_operation(ast)
17
41
  when Literal
18
- ast.type
42
+ check_literal(ast)
43
+ when Message
44
+ check_message(ast)
19
45
  when Identifier
20
46
  check_identifier(ast)
21
47
  when Condition
@@ -28,14 +54,32 @@ module Cel
28
54
  private
29
55
 
30
56
  def merge(declarations)
31
- Checker.new(@declarations ? @declarations.merge(declarations) : declarations)
57
+ Checker.new(@environment, @declarations ? @declarations.merge(declarations) : declarations)
32
58
  end
33
59
 
34
- LOGICAL_EXPECTED_TYPES = %i[bool int uint double string bytes timestamp duration].freeze
35
- ADD_EXPECTED_TYPES = %i[int uint double string bytes list duration].freeze
36
- SUB_EXPECTED_TYPES = %i[int uint double duration].freeze
37
- MULTIDIV_EXPECTED_TYPES = %i[int uint double].freeze
38
- REMAINDER_EXPECTED_TYPES = %i[int uint].freeze
60
+ def check_literal(literal)
61
+ case literal
62
+ when List
63
+ lit = ListType.new(literal.value)
64
+ elem_types = literal.value.map(&method(:call))
65
+
66
+ if !literal.value.empty? && elem_types.map do |type|
67
+ type.respond_to?(:to_sym) ? type.to_sym : type
68
+ end.uniq.size == 1
69
+ elem = literal.by_max_depth
70
+ lit.element_type = call(elem)
71
+ end
72
+ lit
73
+ when Map
74
+ MapType.new(literal.value)
75
+ else
76
+ literal.type
77
+ end
78
+ end
79
+
80
+ def check_message(literal)
81
+ literal.type
82
+ end
39
83
 
40
84
  def check_operation(operation)
41
85
  type = infer_operation_type(operation)
@@ -43,8 +87,6 @@ module Cel
43
87
  type
44
88
  end
45
89
 
46
- BOOLABLE_OPERATORS = %w[&& || == != < <= >= >].freeze
47
-
48
90
  def infer_operation_type(operation)
49
91
  op = operation.op
50
92
 
@@ -78,11 +120,18 @@ module Cel
78
120
  return TYPES[:bool] if values.uniq.size == 1 ||
79
121
  values.all? { |v| v == :list } ||
80
122
  values.all? { |v| v == :map } ||
123
+ # comparison with null is allowed for legacy reasons
124
+ # https://github.com/google/cel-spec/issues/361
125
+ values.one? { |v| v == :null_type } ||
81
126
  values.include?(:any)
82
127
  when "in"
83
128
  return TYPES[:bool] if find_match_all_types(%i[list map any], values.last)
84
129
  when "+"
85
- return type if (type = find_match_all_types(ADD_EXPECTED_TYPES, values))
130
+ if (type = find_match_all_types(ADD_EXPECTED_TYPES, values))
131
+ return type unless type == :list
132
+
133
+ return values.find { |v| v.element_type != :any } || type
134
+ end
86
135
 
87
136
  return TYPES[:timestamp] if %i[timestamp duration].any? { |typ| values.first == typ }
88
137
 
@@ -127,6 +176,12 @@ module Cel
127
176
  check_identifier(var)
128
177
  when Invoke
129
178
  check_invoke(var)
179
+ when Literal
180
+ check_literal(var)
181
+ when Group
182
+ check(var.value)
183
+ when Protobuf.base_class
184
+ var.class
130
185
  else
131
186
  var.type
132
187
  end
@@ -147,9 +202,15 @@ module Cel
147
202
  # to maps. For maps, selection is interpreted as the field being a string key.
148
203
  case func
149
204
  when :[]
205
+ if args.is_a?(Invoke)
206
+ args = call(args)
207
+ return TYPES[:any] if args == TYPES[:any]
208
+ end
150
209
  attribute = var_type.get(args)
151
210
  return TYPES[:any] unless attribute
152
- when :all, :exists, :exists_one
211
+ when :exists, :all
212
+ return TYPES[:bool]
213
+ when :exists_one
153
214
  check_arity(funcall, args, 2)
154
215
  identifier, predicate = args
155
216
 
@@ -169,10 +230,25 @@ module Cel
169
230
  when ListType
170
231
  case func
171
232
  when :[]
233
+ if args.is_a?(Invoke)
234
+ args = call(args)
235
+ return TYPES[:any] if args == TYPES[:any]
236
+ end
237
+
238
+ if args.is_a?(Type)
239
+ unsupported_operation(funcall) unless args == TYPES[:int]
240
+
241
+ return var_type.element_type
242
+ end
243
+
172
244
  attribute = var_type.get(args)
245
+
173
246
  unsupported_operation(funcall) unless attribute
247
+
174
248
  call(attribute)
175
- when :all, :exists, :exists_one
249
+ when :exists, :all
250
+ TYPES[:bool]
251
+ when :exists_one
176
252
  check_arity(funcall, args, 2)
177
253
  identifier, predicate = args
178
254
 
@@ -227,7 +303,8 @@ module Cel
227
303
  when :getDate, :getDayOfMonth, :getDayOfWeek, :getDayOfYear, :getFullYear, :getHours,
228
304
  :getMilliseconds, :getMinutes, :getMonth, :getSeconds
229
305
  check_arity(func, args, 0..1)
230
- return TYPES[:int] if args.empty? || (args.size.positive? && args[0] == :string)
306
+ # TODO: verify if it's a valid tz
307
+ return TYPES[:int] if args.empty? || (args.size.positive? && call(args.first) == :string)
231
308
  else
232
309
  unsupported_type(funcall)
233
310
  end
@@ -241,25 +318,41 @@ module Cel
241
318
  unsupported_type(funcall)
242
319
  end
243
320
  unsupported_operation(funcall)
321
+ when Class
322
+ # Protobuf property
323
+ if var_type.respond_to?(:descriptor)
324
+ return Protobuf.convert_to_cel_type(
325
+ var_type.descriptor.lookup(func.to_s),
326
+ @environment.package
327
+ )
328
+ end
329
+
330
+ TYPES[:any]
331
+ when Module
332
+ # Protobuf enum access
333
+ return TYPES[:int] if var_type.const_defined?(func)
334
+
335
+ TYPES[:any]
244
336
  else
245
337
  TYPES[:any]
246
338
  end
247
339
  end
248
340
 
249
- CAST_ALLOWED_TYPES = {
250
- int: %i[uint double string timestamp], # TODO: enum
251
- uint: %i[int double string],
252
- string: %i[int uint double bytes timestamp duration],
253
- double: %i[int uint string],
254
- bytes: %i[string],
255
- duration: %i[string],
256
- timestamp: %i[string],
257
- }.freeze
258
-
259
341
  def check_standard_func(funcall)
260
342
  func = funcall.func
261
343
  args = funcall.args
262
344
 
345
+ # check if protobuf conversion
346
+ if (proto_type = Protobuf.convert_to_proto_type(func.to_s, @environment.package))
347
+ # TODO: fixit
348
+ raise "unsupported" unless proto_type.respond_to?(:resolve)
349
+
350
+ check_arity(func, args, 1)
351
+
352
+ arg = call(args.first)
353
+ return proto_type.descriptor.name if find_match_all_types(%i[int], arg)
354
+ end
355
+
263
356
  case func
264
357
  when :type
265
358
  check_arity(func, args, 1)
@@ -279,16 +372,23 @@ module Cel
279
372
  allowed_types = CAST_ALLOWED_TYPES[func]
280
373
 
281
374
  arg = call(args.first)
375
+
282
376
  return TYPES[func] if find_match_all_types(allowed_types, arg)
283
377
  when :matches
284
378
  check_arity(func, args, 2)
285
379
  return TYPES[:bool] if find_match_all_types(%i[string], args.map(&method(:call)))
286
380
  when :dyn
287
381
  check_arity(func, args, 1)
288
- arg_type = call(args.first)
382
+ arg_type = begin
383
+ call(args.first)
384
+ rescue StandardError
385
+ TYPES[:any]
386
+ end
289
387
  case arg_type
290
388
  when ListType, MapType
291
389
  arg_type.element_type = TYPES[:any]
390
+ else
391
+ return TYPES[:any]
292
392
  end
293
393
  return arg_type
294
394
  else
@@ -326,6 +426,11 @@ module Cel
326
426
 
327
427
  return TYPES[:type] if Cel::PRIMITIVE_TYPES.include?(identifier.to_sym)
328
428
 
429
+ proto_type = identifier.try_convert_to_proto_type
430
+
431
+ # protobuf enum
432
+ return proto_type if proto_type
433
+
329
434
  id_type = infer_dec_type(identifier.id)
330
435
 
331
436
  return TYPES[:any] unless id_type
@@ -341,11 +446,9 @@ module Cel
341
446
  raise CheckError, "`#{condition.if}` must evaluate to a bool" unless if_type == :bool
342
447
 
343
448
  then_type = call(condition.then)
344
- else_type = call(condition.else)
345
-
346
- return then_type if then_type == else_type
449
+ call(condition.else)
347
450
 
348
- TYPES[:any]
451
+ then_type
349
452
  end
350
453
 
351
454
  def infer_dec_type(id)
@@ -391,7 +494,7 @@ module Cel
391
494
  end
392
495
 
393
496
  def unsupported_type(op)
394
- raise NoMatchingOverloadError, op
497
+ raise CheckError, "no matching overload: #{op}"
395
498
  end
396
499
 
397
500
  def unsupported_operation(op)
data/lib/cel/context.rb CHANGED
@@ -11,8 +11,17 @@ module Cel
11
11
  return unless @bindings
12
12
 
13
13
  @bindings.each do |k, v|
14
- val = to_cel_type(v)
15
- val = TYPES[@declarations[k]].cast(val) if @declarations && @declarations.key?(k)
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
16
25
  @bindings[k] = val
17
26
  end
18
27
  end
@@ -20,12 +29,52 @@ module Cel
20
29
  def lookup(identifier)
21
30
  raise EvaluateError, "no value in context for #{identifier}" unless @bindings
22
31
 
23
- id = identifier.id
24
- val = @bindings.dig(*id.split(".").map(&:to_sym))
32
+ id = identifier.to_s
33
+
34
+ lookup_keys = id.split(".")
35
+
36
+ val = @bindings
37
+
38
+ loop do
39
+ fetched = false
40
+ (0...lookup_keys.size).reverse_each do |idx|
41
+ key = lookup_keys[0..idx].join(".").to_sym
42
+
43
+ val = if val.respond_to?(:key?)
44
+ # hash
45
+ next unless val.key?(key)
46
+
47
+ val[key]
48
+ else
49
+ next unless val.respond_to?(key)
50
+
51
+ case val
52
+ when Protobuf.base_class
53
+ Protobuf.lookup(val, key)
54
+ else
55
+ val.__send__(key)
56
+ end
57
+ end
58
+
59
+ lookup_keys = lookup_keys[idx + 1..]
60
+ fetched = true
61
+ break
62
+ end
63
+
64
+ break unless fetched
65
+
66
+ break if lookup_keys.empty?
67
+ end
68
+
69
+ raise EvaluateError, "no value in context for #{id}" if val == @bindings
70
+
71
+ # lookup_keys.each do |key|
72
+ # raise EvaluateError, "no value in context for #{id}" unless val.key?(key)
25
73
 
26
- raise EvaluateError, "no value in context for #{identifier}" unless val
74
+ # val = val[key]
75
+ # end
27
76
 
28
- val
77
+ [val, lookup_keys.empty? ? nil : lookup_keys.join(".")]
29
78
  end
30
79
 
31
80
  def merge(bindings)
data/lib/cel/encoder.rb CHANGED
@@ -38,20 +38,30 @@ module Cel
38
38
  expr.type.to_s,
39
39
  expr.id.to_s,
40
40
  ]
41
- when List
42
- ["lit",
43
- expr.type.to_s,
44
- *expr.value.map(&method(:encode))]
45
- when Map
46
- ["lit",
47
- expr.type.to_s,
48
- *expr.value.map { |kv| kv.map(&method(:encode)) }]
41
+ when List, Map
42
+ [
43
+ "lit",
44
+ expr.type.to_s,
45
+ *encode(expr.value),
46
+ ]
47
+ when Message
48
+ [
49
+ "message",
50
+ expr.name.to_s,
51
+ encode(expr.struct),
52
+ ]
49
53
  when Number, Bool, String, Bytes
50
54
  ["lit", expr.type.to_s, expr.value]
51
55
  when Null
52
56
  %w[lit null]
53
57
  when Type
54
58
  ["lit", "type", expr.to_s]
59
+ when Array
60
+ expr.map(&method(:encode))
61
+ when Hash
62
+ expr.to_h { |*kv| kv.map(&method(:encode)) }
63
+ else
64
+ expr
55
65
  end
56
66
  end
57
67
 
@@ -80,22 +90,30 @@ module Cel
80
90
  id.type = TYPES[type.to_sym]
81
91
  id
82
92
  in ["lit", "list", *items]
83
- list = List.new(items.map(&method(:decode)))
84
- list
93
+ List.new(items.map(&method(:decode)))
94
+
85
95
  in ["lit", "map", items]
86
- Map.new(items.map(&method(:decode)).each_slice(2))
96
+ Map.new(decode(items))
87
97
  in ["lit", /\Aint|uint|double\z/ => type, Integer => val]
88
98
  Number.new(type.to_sym, val)
89
99
  in ["lit", "bool", val]
90
- Bool.new(val)
100
+ Bool.cast(val)
91
101
  in ["lit", "string", val]
92
102
  String.new(val)
93
103
  in ["lit", "bytes", val]
94
104
  Bytes.new(val)
95
105
  in ["lit", "null"]
96
- Null.new
106
+ Null::INSTANCE
97
107
  in ["lit", "type", type]
98
108
  TYPES[type.to_sym]
109
+ in ["message", name, struct]
110
+ Cel::Message.new(name, decode(struct))
111
+ in **items
112
+ items.to_h { |*kv| kv.map(&method(:decode)) }
113
+ in *items
114
+ items.map(&method(:decode)).each_slice(2)
115
+ else
116
+ enc
99
117
  end
100
118
  end
101
119
  end