cel 0.2.3 → 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.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "google/protobuf/descriptor_pb"
3
4
  require "google/protobuf/struct_pb"
4
5
  require "google/protobuf/wrappers_pb"
5
6
  require "google/protobuf/any_pb"
@@ -7,127 +8,474 @@ require "google/protobuf/well_known_types"
7
8
 
8
9
  module Cel
9
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
+
10
30
  module_function
11
31
 
12
- def convert_from_protobuf(msg)
32
+ def base_class
33
+ Google::Protobuf::MessageExts
34
+ end
35
+
36
+ def map_class
37
+ Google::Protobuf::Map
38
+ end
39
+
40
+ def timestamp_class
41
+ Google::Protobuf::Timestamp
42
+ end
43
+
44
+ def duration_class
45
+ Google::Protobuf::Duration
46
+ end
47
+
48
+ def enum_class
49
+ Enum
50
+ end
51
+
52
+ def try_convert_from_wrapper(msg)
13
53
  case msg
14
54
  when Google::Protobuf::Any
55
+ raise EvaluateError, "error conversion for empty Any" unless msg.type_name
15
56
 
16
- any.unpack(type_msg).to_ruby
57
+ type_msg = Object.const_get(Cel.package_to_module_name(msg.type_name))
58
+ try_convert_from_wrapper(msg.unpack(type_msg))
17
59
  when Google::Protobuf::ListValue
18
60
  msg.to_a
19
61
  when Google::Protobuf::Struct
20
62
  msg.to_h
21
63
  when Google::Protobuf::Value
22
- msg.to_ruby
64
+ msg.to_ruby(true)
65
+ when Google::Protobuf::BytesValue
66
+ Cel::Bytes.new(msg.value.bytes)
67
+ when Google::Protobuf::Int32Value,
68
+ Google::Protobuf::Int64Value
69
+ Cel::Number.new(:int, msg.value)
70
+ when Google::Protobuf::UInt32Value,
71
+ Google::Protobuf::UInt64Value
72
+ Cel::Number.new(:uint, msg.value)
73
+ when Google::Protobuf::DoubleValue,
74
+ Google::Protobuf::FloatValue
75
+ Cel::Number.new(:double, msg.value)
23
76
  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
77
  Google::Protobuf::NullValue,
32
78
  Google::Protobuf::StringValue
79
+ # conversion to cel type won't be ambiguous here
33
80
  msg.value
34
81
  when Google::Protobuf::Timestamp
35
82
  msg.to_time
36
83
  when Google::Protobuf::Duration
37
84
  Cel::Duration.new(seconds: msg.seconds, nanos: msg.nanos)
38
85
  else
39
- raise Error, "#{msg.class}: protobuf to cel unsupported"
86
+ msg.extend(CelComparisonMode)
87
+ msg
40
88
  end
41
89
  end
42
90
 
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 = case key
64
- when "null_value"
65
- Null.new if value.zero?
66
- when "number_value"
67
- value = -value.operands.first if value.is_a?(Operation) && value.op == "-" && value.unary?
68
- Number.new(:double, value)
69
- else
70
- value
91
+ def try_convert_from_wrapper_type(*)
92
+ TYPES[:map]
93
+ end
94
+
95
+ def convert_to_proto_type(type, package)
96
+ proto_type = Cel.package_to_module_name(type)
97
+
98
+ return unless proto_type
99
+
100
+ return Object.const_get(proto_type) if Object.const_defined?(proto_type)
101
+
102
+ return unless package
103
+
104
+ return unless package.const_defined?(proto_type)
105
+
106
+ package.const_get(proto_type)
107
+ end
108
+
109
+ def convert_to_cel_type(proto, package)
110
+ case proto
111
+ when Google::Protobuf::FieldDescriptor
112
+
113
+ cel_type =
114
+ case proto.type
115
+ when :int32, :int64, :sint32, :sint64, :sfixed32, :sfixed64, :enum
116
+ TYPES[:int]
117
+ when :uint32, :uint64, :fixed32, :fixed64
118
+ TYPES[:uint]
119
+ when :float, :double
120
+ TYPES[:double]
121
+ when :bool
122
+ TYPES[:bool]
123
+ when :string
124
+ TYPES[:string]
125
+ when :bytes
126
+ TYPES[:bytes]
127
+ when :message
128
+ proto_type = convert_to_proto_type(proto.submsg_name, package)
129
+
130
+ proto_type = PROTO_TO_CEL_TYPE[proto_type] if PROTO_TO_CEL_TYPE.key?(proto_type)
131
+ proto_type
132
+ end
133
+
134
+ if proto.label == :repeated
135
+ return TYPES[:list, cel_type] unless proto.subtype
136
+
137
+ case proto.subtype.options
138
+ when Google::Protobuf::EnumOptions
139
+ return TYPES[:list, cel_type]
140
+ when Google::Protobuf::MessageOptions
141
+ # protobuf doesn't have a way to identify whether this field descriptors is from a map,
142
+ # nor the types for key and values.
143
+ return TYPES[:map, cel_type] if proto.subtype.options.map_entry
144
+ end
145
+
146
+ return TYPES[:list, cel_type]
71
147
  end
72
- when "BoolValue", "google.protobuf.BoolValue"
73
- value = value.nil? ? Bool.new(false) : value[Identifier.new("value")]
74
- when "BytesValue", "google.protobuf.BytesValue"
75
- value = value[Identifier.new("value")]
76
- when "DoubleValue", "google.protobuf.DoubleValue",
77
- "FloatValue", "google.protobuf.FloatValue"
78
- value = value.nil? ? Number.new(:double, 0.0) : value[Identifier.new("value")]
79
- value.type = TYPES[:double]
80
- when "Int32Value", "google.protobuf.Int32Value",
81
- "Int64Value", "google.protobuf.Int64Value"
82
- value = value.nil? ? Number.new(:int, 0) : value[Identifier.new("value")]
83
- when "Uint32Value", "google.protobuf.UInt32Value",
84
- "Uint64Value", "google.protobuf.UInt64Value"
85
- value = value.nil? ? Number.new(:uint, 0) : value[Identifier.new("value")]
86
- when "NullValue", "google.protobuf.NullValue"
87
- value = Null.new
88
- when "StringValue", "google.protobuf.StringValue"
89
- value = value.nil? ? String.new(+"") : value[Identifier.new("value")]
90
- when "Timestamp", "google.protobuf.Timestamp"
91
- seconds = value.fetch(Identifier.new("seconds"), 0)
92
- nanos = value.fetch(Identifier.new("nanos"), 0)
93
- value = Timestamp.new(Time.at(seconds, nanos, :nanosecond))
94
- when "Duration", "google.protobuf.Duration"
95
- seconds = value.fetch(Identifier.new("seconds"), 0)
96
- nanos = value.fetch(Identifier.new("nanos"), 0)
97
- value = Duration.new(seconds: seconds, nanos: nanos)
148
+
149
+ cel_type
150
+ else
151
+ PROTO_TO_CEL_TYPE[proto]
152
+ end
153
+ end
154
+
155
+ def convert_to_proto(proto_type, values, convert_value_to_proto: false)
156
+ return values.unpack(proto_type) if values.is_a?(Google::Protobuf::Any)
157
+
158
+ if proto_type == Google::Protobuf::Struct
159
+ values = values[:fields] if values.key?(:fields)
160
+
161
+ raise EvaluateError, "bad key type" unless values.all? { |k, _| k.is_a?(::String) }
162
+
163
+ proto_type.from_hash(values)
164
+ elsif proto_type == Google::Protobuf::ListValue
165
+ if values.is_a?(Hash) && values.key?(:values)
166
+ proto_type.from_a(values[:values])
167
+ else
168
+ proto_type.from_a(values)
169
+ end
170
+ elsif proto_type == Google::Protobuf::NullValue
171
+ nil
172
+ elsif proto_type == Google::Protobuf::Value
173
+ case values
174
+ when Google::Protobuf::Duration
175
+ proto_type.new(string_value: "#{values.seconds}#{if values.nanos.positive?
176
+ ".#{values.nanos / 1_000_000_000.0}"
177
+ end}s")
178
+ when Google::Protobuf::Timestamp
179
+ proto_type.new(string_value: values.to_time.utc.iso8601(9))
180
+ when Hash
181
+ if values.key?(:struct_value)
182
+
183
+ raise EvaluateError, "bad key type" unless values[:struct_value].all? { |k, _| k.is_a?(::String) }
184
+
185
+ values[:struct_value] = Google::Protobuf::Struct.from_hash(values[:struct_value])
186
+ elsif values.key?(:list_value)
187
+ values[:list_value] = Google::Protobuf::ListValue.from_a(values[:list_value])
188
+ elsif values.empty?
189
+ if convert_value_to_proto
190
+ values = { struct_value: Google::Protobuf::Struct.from_hash(values) }
191
+ else
192
+ values[:null_value] = Google::Protobuf::NullValue::NULL_VALUE
193
+ end
194
+ elsif values.any? { |k, _| proto_type.descriptor.lookup(k.to_s).nil? }
195
+ raise EvaluateError, "bad key type" unless values.all? { |k, _| k.is_a?(::String) }
196
+
197
+ values = { struct_value: Google::Protobuf::Struct.from_hash(values) }
198
+ end
199
+ proto_type.new(values)
200
+ when Google::Protobuf::BoolValue
201
+ proto_type.new(bool_value: values.value)
202
+ when Google::Protobuf::DoubleValue,
203
+ Google::Protobuf::FloatValue,
204
+ Google::Protobuf::Int32Value,
205
+ Google::Protobuf::UInt32Value
206
+ proto_type.new(number_value: values.value)
207
+ when Google::Protobuf::Int64Value,
208
+ Google::Protobuf::UInt64Value
209
+ if values.value >= MAX_INT - 1
210
+ proto_type.new(string_value: values.value.to_s)
211
+ else
212
+ proto_type.new(number_value: values.value)
213
+ end
214
+ when Google::Protobuf::StringValue
215
+ proto_type.new(string_value: values.value)
216
+ when Google::Protobuf::BytesValue
217
+ proto_type.new(string_value: [values.value].pack("m0"))
218
+ when Google::Protobuf::NullValue
219
+ proto_type.new(null_value: Google::Protobuf::NullValue::NULL_VALUE)
220
+ when Google::Protobuf::Struct
221
+ proto_type.new(struct_value: values.value)
222
+ when Google::Protobuf::ListValue
223
+ proto_type.new(list_value: values.value)
224
+ when Google::Protobuf::Empty
225
+ proto_type.from_ruby({})
226
+ when Google::Protobuf::FieldMask
227
+ proto_type.from_ruby(values.paths.join(","))
228
+ else
229
+ proto_type.from_ruby(values)
230
+ end
231
+ elsif proto_type == Google::Protobuf::Any
232
+ case values
233
+ when Array
234
+ proto_type.pack(Google::Protobuf::ListValue.from_a(values))
235
+ when Hash
236
+ # type_url: "", value: ""
237
+ # TODO: consider pattern matching
238
+ if convert_value_to_proto
239
+ proto_type.pack(Google::Protobuf::Struct.from_hash(values))
240
+ else
241
+ proto_type.new(values)
242
+ end
243
+ when base_class
244
+ proto_type.pack(values)
245
+ else
246
+ proto_type.pack(Google::Protobuf::Value.from_ruby(values))
247
+ end
248
+ elsif proto_type == Google::Protobuf::Timestamp
249
+ case values
250
+ when Time
251
+ proto_type.from_time(values)
252
+ when nil
253
+ nil
254
+ else
255
+ proto_type.new(values)
256
+ end
257
+ elsif proto_type == Google::Protobuf::Duration
258
+ case values
259
+ when Numeric
260
+ seconds, nanos = values.divmod(1)
261
+ nanos *= 1_000_000_000
262
+ proto_type.new(seconds: seconds, nanos: nanos)
263
+ when nil
264
+ nil
265
+ else
266
+ proto_type.new(values)
267
+ end
268
+ elsif values.is_a?(Hash)
269
+ values = values.to_h do |key, value|
270
+ field_descriptor = proto_type.descriptor.lookup(key.to_s)
271
+ if value.nil? && (NILLABLE_WRAPPERS.include?(field_descriptor.subtype.msgclass) ||
272
+ proto_type.descriptor.enum_for(:each_oneof).one? do |oneof|
273
+ oneof.enum_for(:each).include?(field_descriptor)
274
+ end)
275
+
276
+ next([key, value])
277
+ end
278
+
279
+ next([key, value]) unless field_descriptor && field_descriptor.type != :enum
280
+
281
+ field_subtype = field_descriptor.subtype
282
+
283
+ next([key, value]) unless field_subtype
284
+
285
+ [
286
+ key,
287
+ # discard protobuf enums as well
288
+ value.is_a?(field_subtype.msgclass) ?
289
+ value :
290
+ convert_to_proto(field_subtype.msgclass, value, convert_value_to_proto: true),
291
+ ]
292
+ end
293
+
294
+ # protobuf sets maps as abstract classes, and there's apparently no way to identify them
295
+ # regardless, since their type is "message". Checking for ruby class name is the best
296
+ # proxy I could come up with.
297
+ return values unless proto_type.name
298
+
299
+ proto_type.new(values)
300
+ elsif values.nil?
301
+ Google::Protobuf::Value.from_ruby(values)
302
+ else
303
+ proto_type.new(value: values)
304
+ end
305
+ end
306
+
307
+ def convert_to_enum(value, proto_type = nil)
308
+ if proto_type
309
+ Enum.new(value, proto_type)
310
+ else
311
+ Enum.new(value)
98
312
  end
99
- value
100
313
  end
101
314
 
102
315
  def try_invoke_from(var, func, args)
103
- case var
104
- when "Any", "google.protobuf.Any",
105
- "ListValue", "google.protobuf.ListValue",
106
- "Struct", "google.protobuf.Struct",
107
- "Value", "google.protobuf.Value",
108
- "BoolValue", "google.protobuf.BoolValue",
109
- "BytesValue", "google.protobuf.BytesValue",
110
- "DoubleValue", "google.protobuf.DoubleValue",
111
- "FloatValue", "google.protobuf.FloatValue",
112
- "Int32Value", "google.protobuf.Int32Value",
113
- "Int64Value", "google.protobuf.Int64Value",
114
- "Uint32Value", "google.protobuf.Uint32Value",
115
- "Uint64Value", "google.protobuf.Uint64Value",
116
- "NullValue", "google.protobuf.NullValue",
117
- "StringValue", "google.protobuf.StringValue",
118
- "Timestamp", "google.protobuf.Timestamp",
119
- "Duration", "google.protobuf.Duration"
120
- protoclass = var.split(".").last
121
- protoclass = Google::Protobuf.const_get(protoclass)
122
-
123
- value = if args.nil? && protoclass.constants.include?(func.to_sym)
124
- protoclass.const_get(func)
316
+ return unless var.respond_to?(:to_str) && PROTOBUF_VALUES.include?(var.to_str)
317
+
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)
325
+ end
326
+
327
+ Literal.to_cel_type(value)
328
+ end
329
+
330
+ # finds value for +attribute+ declared in the +proto+.
331
+ #
332
+ # if none is found, and if +attribute+ is another proto message, then return an instance of the type.
333
+ # if field is an enum,
334
+ def lookup(proto, attribute)
335
+ value = proto.public_send(attribute)
336
+
337
+ field = proto.class.descriptor.lookup(attribute.to_s)
338
+
339
+ case field.label
340
+ when :repeated
341
+ case value
342
+ when Google::Protobuf::Map
343
+ value.to_h { |k, v| [k, convert_from_proto_field_type(field, v)] } # rubocop:disable Style/HashTransformValues
125
344
  else
126
- protoclass.__send__(func, *args)
345
+ value.map { |v| convert_from_proto_field_type(field, v) }
127
346
  end
347
+ else
348
+ convert_from_proto_field_type(field, value)
349
+ end
350
+ end
128
351
 
129
- Literal.to_cel_type(value)
352
+ def convert_from_proto_field_type(field, value)
353
+ if value.nil? && (field.type == :message)
354
+ subtype = field.subtype.msgclass
355
+
356
+ if NILLABLE_WRAPPERS.include?(subtype) ||
357
+ subtype == Google::Protobuf::Value
358
+ # if a wrapper type, ignore null
359
+ else
360
+ value = subtype.new
361
+ end
362
+ end
363
+
364
+ case field.type
365
+ when :message
366
+ value
367
+ when :enum
368
+ enum_type = const_get(Cel.package_to_module_name(field.submsg_name))
369
+
370
+ return Enum.new(value, enum_type) if value.is_a?(Integer)
371
+
372
+ enum_mod = Cel.package_to_module_name(field.submsg_name)
373
+
374
+ Enum.new(const_get(enum_mod).resolve(value), enum_type)
375
+ when :int32, :int64, :sint32, :sint64, :sfixed32, :sfixed64
376
+ return unless value
377
+
378
+ Number.new(:int, value)
379
+ when :uint32, :uint64, :fixed32, :fixed64
380
+ return unless value
381
+
382
+ Number.new(:uint, value)
383
+ when :float, :double
384
+ return unless value
385
+
386
+ Number.new(:double, value)
387
+ when :bool
388
+ return unless value
389
+
390
+ Bool.cast(value)
391
+ when :string
392
+ return unless value
393
+
394
+ String.new(value)
395
+ when :bytes
396
+ return unless value
397
+
398
+ Bytes.new(value)
399
+ end
400
+ end
401
+
402
+ module CelComparisonMode
403
+ def ==(other)
404
+ super || begin
405
+ return false unless other.is_a?(Protobuf.base_class)
406
+
407
+ a1 = self
408
+ a2 = other
409
+
410
+ if a1.is_a?(Google::Protobuf::Any) && !a1.type_url.empty?
411
+ a1 = a1.unpack(Object.const_get(Cel.package_to_module_name(a1.type_name)))
412
+ a1.extend(CelComparisonMode)
413
+ end
414
+
415
+ if a2.is_a?(Google::Protobuf::Any) && !a2.type_url.empty?
416
+ a2 = a2.unpack(Object.const_get(Cel.package_to_module_name(a2.type_name)))
417
+ end
418
+
419
+ return false unless a1.class == a2.class
420
+
421
+ if a1.is_a?(Google::Protobuf::Any) && a1.type_url.empty?
422
+ a2.is_a?(Google::Protobuf::Any) && a2.type_url.empty?
423
+ return a1.value == a2.value
424
+ end
425
+
426
+ a1.class.descriptor.each do |field|
427
+ f1 = a1.public_send(field.name)
428
+ f1.extend(CelComparisonMode) if f1.is_a?(Protobuf.base_class)
429
+ f2 = a2.public_send(field.name)
430
+ return false unless f1 == f2
431
+ end
432
+ true
433
+ end
130
434
  end
131
435
  end
436
+
437
+ class Enum < Cel::Number
438
+ # protocol buffer enum fields can accept any signed 32-bit number, values outside that range will raise an error.
439
+ MAX_ENUM_INT = (2**31)
440
+
441
+ def initialize(value, enum_type = nil)
442
+ super(:int, value)
443
+ @enum_type = enum_type
444
+ check_overflow(@value)
445
+ end
446
+
447
+ def enum_type
448
+ @enum_type || type
449
+ end
450
+
451
+ private
452
+
453
+ def check_overflow(value)
454
+ raise EvaluateError, "return error for overflow" unless (-(MAX_ENUM_INT - 1)...MAX_ENUM_INT).cover?(value)
455
+ end
456
+ end
457
+
458
+ PROTO_TO_CEL_TYPE = {
459
+ Google::Protobuf::Any => TYPES[:any],
460
+ Google::Protobuf::Value => TYPES[:any],
461
+ Google::Protobuf::ListValue => TYPES[:list],
462
+ Google::Protobuf::Struct => TYPES[:map],
463
+ Google::Protobuf::BoolValue => TYPES[:bool],
464
+ Google::Protobuf::BytesValue => TYPES[:bytes],
465
+ Google::Protobuf::DoubleValue => TYPES[:double],
466
+ Google::Protobuf::FloatValue => TYPES[:double],
467
+ Google::Protobuf::Int32Value => TYPES[:int],
468
+ Google::Protobuf::Int64Value => TYPES[:int],
469
+ Google::Protobuf::UInt32Value => TYPES[:uint],
470
+ Google::Protobuf::UInt64Value => TYPES[:uint],
471
+ Google::Protobuf::NullValue => TYPES[:null],
472
+ Google::Protobuf::StringValue => TYPES[:string],
473
+ Google::Protobuf::Timestamp => TYPES[:timestamp],
474
+ Google::Protobuf::Duration => TYPES[:duration],
475
+ }.freeze
476
+
477
+ NILLABLE_WRAPPERS = (PROTO_TO_CEL_TYPE.keys - [Google::Protobuf::Value, Google::Protobuf::ListValue,
478
+ Google::Protobuf::Struct, Google::Protobuf::Duration,
479
+ Google::Protobuf::Timestamp]).freeze
132
480
  end
133
481
  end