cel 0.3.1 → 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.
@@ -1,834 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "time"
4
- require "delegate"
5
-
6
3
  module Cel
7
4
  LOGICAL_OPERATORS = %w[<= >= < > == != in].freeze
8
5
  MULTI_OPERATORS = %w[* / %].freeze
9
-
10
- class Identifier < SimpleDelegator
11
- attr_reader :id
12
-
13
- attr_accessor :type
14
-
15
- def initialize(identifier, package = nil)
16
- @id = identifier
17
- @type = TYPES[:any]
18
- @package = package
19
- super(@id)
20
- end
21
-
22
- def ==(other)
23
- super || other.to_s == @id.to_s
24
- end
25
-
26
- def to_s
27
- @id.to_s
28
- end
29
-
30
- alias_method :to_ruby_type, :to_s
31
-
32
- def try_convert_to_proto_type
33
- Protobuf.convert_to_proto_type(@id.to_s, @package)
34
- end
35
- end
36
-
37
- class Message
38
- EMPTY_STRUCT = {}.freeze
39
-
40
- attr_reader :name, :struct, :package, :depth
41
-
42
- def initialize(name, struct, package: nil, depth: 1)
43
- @struct = struct ? struct.transform_keys(&:to_sym) : EMPTY_STRUCT
44
- @name = name
45
- @package = package
46
- @depth = depth
47
- end
48
-
49
- def type
50
- @type ||= if proto_type
51
- Protobuf.convert_to_cel_type(proto_type, @package) || proto_type
52
- else
53
- Protobuf.try_convert_from_wrapper_type(@name.to_s)
54
- end
55
- end
56
-
57
- def message_type
58
- @message_type ||= proto_type || container
59
- end
60
-
61
- def proto_type
62
- @proto_type ||= Protobuf.convert_to_proto_type(@name.to_s, @package)
63
- end
64
-
65
- def ==(other)
66
- other.is_a?(Cel::Message) && other.name == @name && other.struct == @struct
67
- end
68
-
69
- def to_s
70
- if proto_type
71
- proto_type.descriptor.name
72
- else
73
- message_type.to_s
74
- end
75
- end
76
-
77
- private
78
-
79
- def container
80
- @container ||= Cel.message_container(@name, @struct)
81
- end
82
- end
83
-
84
- class Invoke
85
- attr_accessor :var
86
-
87
- attr_reader :func, :args, :depth
88
-
89
- def self.new(func:, var: nil, args: nil, **rest)
90
- Protobuf.try_invoke_from(var, func, args) || super
91
- end
92
-
93
- def type
94
- TYPES[:any]
95
- end
96
-
97
- def initialize(func:, var: nil, args: nil, depth: 1, package: nil)
98
- @var = var
99
- @func = func.to_sym
100
- @args = args
101
- @package = package
102
- @depth = depth
103
- end
104
-
105
- def ==(other)
106
- case other
107
- when Invoke
108
- @var == other.var && @func == other.func && @args == other.args
109
- when Array
110
- [@var, @func, @args].compact == other
111
- else
112
- super
113
- end
114
- end
115
-
116
- def to_s
117
- if var
118
- if indexing?
119
- "#{var}[#{args}]"
120
- else
121
- "#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
122
- end
123
- else
124
- "#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
125
- end
126
- end
127
-
128
- def try_convert_to_proto_type
129
- return unless @var
130
-
131
- proto_type = Protobuf.convert_to_proto_type(@var.to_s, @package)
132
-
133
- return unless proto_type
134
-
135
- return proto_type unless proto_type.const_defined?(@func)
136
-
137
- proto_type.const_get(@func)
138
- end
139
-
140
- def indexing?
141
- @func == :[]
142
- end
143
- end
144
-
145
- class Function
146
- attr_reader :label, :types, :type
147
-
148
- def initialize(*types, label: nil, return_type: nil, &func)
149
- unless func.nil?
150
- types = Array.new(func.arity) { TYPES[:any] } if types.empty?
151
- raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
152
- end
153
- @types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
154
- @type = if return_type.nil?
155
- TYPES[:any]
156
- else
157
- return_type.is_a?(Type) ? return_type : TYPES[return_type]
158
- end
159
- @func = func
160
- @label = label
161
- end
162
-
163
- def call(*args)
164
- Literal.to_cel_type(@func.call(*args))
165
- end
166
- end
167
-
168
- mod = self
169
- mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
170
- mod::Function.new(*args, **kwargs, &blk)
171
- end
172
-
173
- class Literal < SimpleDelegator
174
- attr_reader :type, :value
175
-
176
- def initialize(type, value)
177
- @type = type.is_a?(Type) ? type : TYPES[type]
178
- @value = value
179
- super(value)
180
- check
181
- end
182
-
183
- def ==(other)
184
- case other
185
- when Literal
186
- @type == other.type && @value == other.value
187
- else
188
- @value == other
189
- end
190
- end
191
-
192
- def self.to_cel_type(val)
193
- if val.is_a?(Protobuf.base_class) ||
194
- val.is_a?(Struct) # no-protobuf mode
195
- val = Protobuf.try_convert_from_wrapper(val)
196
-
197
- return val if val.is_a?(Protobuf.base_class) # still
198
- end
199
-
200
- case val
201
- when Literal, Identifier, Invoke, Operation, Condition, Group, # already cel
202
- Protobuf.enum_class # already a usable class a la protobuf
203
- val
204
- when Protobuf.map_class
205
- Map.new(val.to_h)
206
- when ::String
207
- String.new(val)
208
- when ::Symbol
209
- Identifier.new(val)
210
- when ::Integer
211
- Number.new(:int, val)
212
- when ::Float, ::BigDecimal
213
- Number.new(:double, val)
214
- when ::Hash
215
- Map.new(val)
216
- when ::Array
217
- List.new(val)
218
- when true, false
219
- Bool.cast(val)
220
- when nil
221
- Null::INSTANCE
222
- when Time
223
- Timestamp.new(val)
224
- else
225
- raise BindingError, "can't convert #{val} to CEL type"
226
- end
227
- end
228
-
229
- alias_method :to_ruby_type, :value
230
-
231
- private
232
-
233
- def check; end
234
- end
235
-
236
- class Number < Literal
237
- def ==(other)
238
- case other
239
- when Number
240
- @value == other.value
241
- else
242
- super
243
- end
244
- end
245
-
246
- ([:+, :-, *(MULTI_OPERATORS - %w[%])] - %i[%]).each do |op|
247
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
248
- def #{op}(other)
249
- value = super
250
- check_overflow(value)
251
- Number.new(@type, value)
252
- end
253
- OUT
254
- end
255
-
256
- def %(other)
257
- raise EvaluateError, "no matching overload" if @type == :double && other.type == :double
258
-
259
- value = if negative?
260
- if other.negative?
261
- -(abs % other.abs)
262
- else
263
- -(abs % other)
264
- end
265
- else
266
- @value % other.abs
267
- end
268
- check_overflow(value)
269
- Number.new(@type, value)
270
- end
271
-
272
- %i[+ -].each do |op|
273
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
274
- def #{op}@
275
- raise EvaluateError, "no such overload" if @type == TYPES[:uint]
276
- value = super
277
- Number.new(@type, value)
278
- end
279
- OUT
280
- end
281
-
282
- def check_overflow(value = @value)
283
- case @type
284
- when TYPES[:double]
285
- return if value.nan? || value.infinite?
286
- raise EvaluateError, "return error for overflow" unless (-(MAX_FLOAT - 1)...MAX_FLOAT).cover?(value)
287
- when TYPES[:uint]
288
- raise EvaluateError, "return error for overflow" unless (0...MAX_INT).cover?(value)
289
- else
290
- raise EvaluateError, "return error for overflow" unless (-(MAX_INT - 1)...MAX_INT).cover?(value)
291
- end
292
- end
293
- end
294
-
295
- class Bool < Literal
296
- def initialize(value)
297
- super(:bool, value)
298
- end
299
-
300
- (LOGICAL_OPERATORS - %w[== != in]).each do |op|
301
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
302
- def #{op}(other)
303
- return super unless other.is_a?(Bool)
304
-
305
- lhs = @value ? 1 : 0
306
- rhs = other.value ? 1 : 0
307
- lhs.__send__(__method__, rhs)
308
- end
309
- OUT
310
- end
311
-
312
- def !
313
- Bool.cast(super)
314
- end
315
-
316
- TRUE_VALUE = new(true)
317
- FALSE_VALUE = new(false)
318
-
319
- def self.cast(val)
320
- val ? TRUE_VALUE : FALSE_VALUE
321
- end
322
- end
323
-
324
- class Null < Literal
325
- def initialize
326
- super(:null_type, nil)
327
- end
328
-
329
- INSTANCE = new.freeze
330
- end
331
-
332
- class String < Literal
333
- def initialize(value)
334
- super(:string, value)
335
- end
336
-
337
- # override base String#%
338
- def %(_other)
339
- raise NoMethodError, "undefined method '%' for #{self}"
340
- end
341
-
342
- # CEL string functions
343
-
344
- def contains(string)
345
- Bool.cast(@value.include?(string))
346
- end
347
-
348
- def endsWith(string)
349
- Bool.cast(@value.end_with?(string))
350
- end
351
-
352
- def startsWith(string)
353
- Bool.cast(@value.start_with?(string))
354
- end
355
-
356
- def matches(pattern)
357
- Macro.matches(self, pattern)
358
- end
359
-
360
- %i[+ -].each do |op|
361
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
362
- def #{op}(other)
363
- String.new(super)
364
- end
365
- OUT
366
- end
367
- end
368
-
369
- class Bytes < Literal
370
- attr_reader :string
371
-
372
- def initialize(value)
373
- super(:bytes, value)
374
- @string = value.pack("C*")
375
- end
376
-
377
- def to_ary
378
- [self]
379
- end
380
-
381
- alias_method :to_ruby_type, :string
382
-
383
- (LOGICAL_OPERATORS - %w[==]).each do |op|
384
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
385
- def #{op}(other)
386
- other.is_a?(Cel::Bytes) ? Bool.cast(@string.__send__(__method__, other.string)) : super
387
- end
388
- OUT
389
- end
390
-
391
- %i[+ -].each do |op|
392
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
393
- def #{op}(other)
394
- Bytes.new(@value + other.value)
395
- end
396
- OUT
397
- end
398
- end
399
-
400
- class List < Literal
401
- attr_reader :depth
402
-
403
- def initialize(value, depth: 1)
404
- value = value.map do |v|
405
- Literal.to_cel_type(v)
406
- end
407
- super(TYPES[:list], value)
408
- @depth = depth
409
- end
410
-
411
- def [](key)
412
- case key
413
- when Number
414
- if key.type == :double
415
- val = key.value
416
-
417
- raise InvalidArgumentError, key unless (val % 1).zero?
418
- end
419
- end
420
-
421
- super
422
- end
423
-
424
- def to_ary
425
- [self]
426
- end
427
-
428
- def to_ruby_type
429
- value.map(&:to_ruby_type)
430
- end
431
-
432
- %i[+ -].each do |op|
433
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
434
- def #{op}(other)
435
- List.new(@value.send(__method__, other.value))
436
- end
437
- OUT
438
- end
439
-
440
- def by_max_depth
441
- max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
442
-
443
- # return the last value if all options have the same depth
444
- return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
445
-
446
- max
447
- end
448
-
449
- private
450
-
451
- def calc_depth(element, acc)
452
- case element
453
- when List
454
- element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
455
- when Map
456
- element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
457
- else
458
- acc
459
- end
460
- end
461
- end
462
-
463
- class Map < Literal
464
- ALLOWED_TYPES = %i[int uint bool string].map { |typ| TYPES[typ] }.freeze
465
-
466
- attr_reader :depth
467
-
468
- def initialize(value, depth: 1)
469
- # store array internally to support repeated keys
470
- value = value.map do |k, v|
471
- [Literal.to_cel_type(k), Literal.to_cel_type(v)]
472
- end
473
- super(TYPES[:map], value)
474
- @depth = depth
475
- end
476
-
477
- def ==(other)
478
- case other
479
- when Map
480
- @value.all? do |args|
481
- other.value.include?(args)
482
- end
483
- when Array
484
- # calls to != will downgrade maps to arrays
485
- @value.all? do |args|
486
- other.include?(args)
487
- end
488
- else
489
- super
490
- end
491
- end
492
-
493
- (LOGICAL_OPERATORS - %w[== != in]).each do |op|
494
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
495
- def #{op}(*)
496
- raise NoMethodError
497
- end
498
- OUT
499
- end
500
-
501
- def key?(key)
502
- @value.any? { |k, _v| k == key }
503
- end
504
-
505
- alias_method :include?, :key?
506
-
507
- def [](key)
508
- values = @value.filter_map { |k, v| v if k == key }
509
-
510
- raise NoSuchKeyError.new(self, key) if values.empty?
511
-
512
- raise EvaluateError, "Failed with repeated key" if values.size > 1
513
-
514
- values.first
515
- end
516
-
517
- def to_ruby_type
518
- value.to_h { |args| args.map(&:to_ruby_type) }
519
- end
520
-
521
- def respond_to_missing?(meth, *args)
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 }
528
- end
529
-
530
- def method_missing(meth, *args)
531
- return super unless @value
532
-
533
- meth_s = meth.to_s
534
- values = @value.filter_map { |k, v| v if k == meth_s }
535
-
536
- return super if values.empty?
537
-
538
- values.first
539
- end
540
-
541
- private
542
-
543
- # For a map, the entry keys are sub-expressions that must evaluate to values
544
- # of an allowed type (int, uint, bool, or string)
545
- def check
546
- return if @value.all? do |key, _|
547
- key.is_a?(Identifier) || ALLOWED_TYPES.include?(key.type)
548
- end
549
-
550
- raise CheckError, "#{self} is invalid (keys must be of an allowed type (int, uint, bool, or string)"
551
- end
552
- end
553
-
554
- class Timestamp < Literal
555
- TIMESTAMP_RANGE = (Time.parse("0001-01-01T00:00:00Z")..Time.parse("9999-12-31T23:59:59.999999999Z"))
556
-
557
- def initialize(value)
558
- value = case value
559
- when ::String then Time.parse(value)
560
- when Numeric then Time.at(value)
561
- else value
562
- end
563
-
564
- raise EvaluateError, "out of range" unless TIMESTAMP_RANGE.include?(value)
565
-
566
- super(:timestamp, value.round(9))
567
- end
568
-
569
- def +(other)
570
- Timestamp.new(@value + other.to_f)
571
- end
572
-
573
- def -(other)
574
- case other
575
- when Timestamp
576
- Duration.new(@value - other.value)
577
- when Duration
578
- Timestamp.new(@value - other.to_f)
579
- end
580
- end
581
-
582
- LOGICAL_OPERATORS.each do |op|
583
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
584
- def #{op}(other)
585
- other.is_a?(Cel::Literal) ? Bool.cast(super) : super
586
- end
587
- OUT
588
- end
589
-
590
- # Cel Functions
591
-
592
- def getDate(tz = nil)
593
- Number.new(:int, to_local_time(tz).day)
594
- end
595
-
596
- def getDayOfMonth(tz = nil)
597
- getDate(tz) - 1
598
- end
599
-
600
- def getDayOfWeek(tz = nil)
601
- Number.new(:int, to_local_time(tz).wday)
602
- end
603
-
604
- def getDayOfYear(tz = nil)
605
- Number.new(:int, to_local_time(tz).yday - 1)
606
- end
607
-
608
- def getMonth(tz = nil)
609
- Number.new(:int, to_local_time(tz).month - 1)
610
- end
611
-
612
- def getFullYear(tz = nil)
613
- Number.new(:int, to_local_time(tz).year)
614
- end
615
-
616
- def getHours(tz = nil)
617
- Number.new(:int, to_local_time(tz).hour)
618
- end
619
-
620
- def getMinutes(tz = nil)
621
- Number.new(:int, to_local_time(tz).min)
622
- end
623
-
624
- def getSeconds(tz = nil)
625
- Number.new(:int, to_local_time(tz).sec)
626
- end
627
-
628
- def getMilliseconds(tz = nil)
629
- Number.new(:int, to_local_time(tz).nsec / 1_000_000)
630
- end
631
-
632
- def to_ruby_type
633
- Protobuf.timestamp_class.from_time(@value)
634
- end
635
-
636
- private
637
-
638
- def to_local_time(tz = nil)
639
- time = @value
640
- if tz
641
- tz = tz.value
642
- if tz.match?(/\A[+-]?\d{2,}:\d{2,}\z/)
643
- tz.prepend("+") unless tz.start_with?("+", "-")
644
- else
645
- tz = TZInfo::Timezone.get(tz)
646
- end
647
- time = time.getlocal(tz)
648
- end
649
- time
650
- end
651
- end
652
-
653
- class Duration < Literal
654
- def initialize(value)
655
- value =
656
- case value
657
- when ::String
658
- init_from_string(value)
659
- when Hash
660
- seconds, nanos = value.values_at(:seconds, :nanos)
661
- seconds ||= 0
662
- nanos ||= 0
663
- seconds + (nanos / 1_000_000_000.0)
664
- else
665
- value
666
- end
667
-
668
- raise EvaluateError, "out of range" unless (value * 1_000_000_000).between?(-MAX_INT, MAX_INT)
669
-
670
- super(:duration, value)
671
- end
672
-
673
- def to_s
674
- seconds = getSeconds
675
- millis = getMilliseconds
676
- millis.positive? ? "#{seconds}s#{millis}m" : "#{seconds}s"
677
- end
678
-
679
- def +(other)
680
- case other
681
- when Cel::Duration
682
- Cel::Duration.new(@value + other.value)
683
- when Cel::Timestamp
684
- Cel::Timestamp.new(other.value + @value)
685
- end
686
- end
687
-
688
- def -(other)
689
- case other
690
- when Cel::Duration
691
- Cel::Duration.new(@value - other.value)
692
- else
693
- raise Error, "invalid operand #{other} for `-`"
694
- end
695
- end
696
-
697
- LOGICAL_OPERATORS.each do |op|
698
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
699
- def #{op}(other)
700
- case other
701
- when Cel::Literal
702
- Bool.cast(super)
703
- when Numeric
704
- @value == other
705
-
706
- else
707
- super
708
- end
709
- end
710
- OUT
711
- end
712
-
713
- # Cel Functions
714
-
715
- def getHours
716
- Cel::Number.new(:int, (getMinutes / 60).to_i)
717
- end
718
-
719
- def getMinutes
720
- Cel::Number.new(:int, (getSeconds / 60).to_i)
721
- end
722
-
723
- def getSeconds
724
- Cel::Number.new(:int, @value.divmod(1).first)
725
- end
726
-
727
- def getMilliseconds
728
- Cel::Number.new(:int, (@value.divmod(1).last * 1000).round)
729
- end
730
-
731
- def to_ruby_type
732
- Protobuf.duration_class.new(seconds: getSeconds.value, nanos: getMilliseconds.value * 1_000_000_000.0)
733
- end
734
-
735
- private
736
-
737
- def init_from_string(value)
738
- seconds = 0
739
- nanos = 0
740
- value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
741
- case units
742
- when "h"
743
- seconds += Cel.to_numeric(duration) * 60 * 60
744
- when "m"
745
- seconds += Cel.to_numeric(duration) * 60
746
- when "s"
747
- seconds += Cel.to_numeric(duration)
748
- when "ms"
749
- nanos += Cel.to_numeric(duration) * 1000 * 1000
750
- when "us"
751
- nanos += Cel.to_numeric(duration) * 1000
752
- when "ns"
753
- nanos += Cel.to_numeric(duration)
754
- else
755
- raise EvaluateError, "#{units} is unsupported"
756
- end
757
- end
758
- duration = seconds + (nanos / 1_000_000_000.0)
759
- duration = -duration if value.start_with?("-")
760
- duration
761
- end
762
- end
763
-
764
- class Group
765
- attr_reader :value
766
-
767
- def initialize(value)
768
- @value = value
769
- end
770
-
771
- def type
772
- TYPES[:any]
773
- end
774
-
775
- def ==(other)
776
- other.is_a?(Group) && @value == other.value
777
- end
778
- end
779
-
780
- class Operation
781
- attr_reader :op, :operands, :depth
782
-
783
- attr_accessor :type
784
-
785
- def initialize(op, operands, depth: 1)
786
- @op = op
787
- @operands = operands
788
- @type = TYPES[:any]
789
- @depth = depth
790
- end
791
-
792
- def ==(other)
793
- case other
794
- when Array
795
- other.size == @operands.size + 1 &&
796
- other.first == @op &&
797
- other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
798
- when Operation
799
- @op == other.op && @type == other.type && @operands == other.operands
800
- else
801
- super
802
- end
803
- end
804
-
805
- def unary?
806
- @operands.size == 1
807
- end
808
-
809
- def to_s
810
- return "#{@op}#{@operands.first}" if @operands.size == 1
811
-
812
- @operands.join(" #{@op} ")
813
- end
814
- end
815
-
816
- class Condition
817
- attr_reader :if, :then, :else, :depth
818
-
819
- def initialize(if_, then_, else_, depth: 1)
820
- @if = if_
821
- @then = then_
822
- @else = else_
823
- @depth = depth
824
- end
825
-
826
- def type
827
- TYPES[:any]
828
- end
829
-
830
- def ==(other)
831
- other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
832
- end
833
- end
834
6
  end
7
+
8
+ require_relative "elements/group"
9
+ require_relative "elements/condition"
10
+ require_relative "elements/operation"
11
+ require_relative "elements/invoke"
12
+ require_relative "elements/message"
13
+ require_relative "elements/identifier"
14
+ require_relative "elements/function"
15
+ require_relative "elements/literal"
16
+ require_relative "elements/null"
17
+ require_relative "elements/number"
18
+ require_relative "elements/bool"
19
+ require_relative "elements/string"
20
+ require_relative "elements/bytes"
21
+ require_relative "elements/list"
22
+ require_relative "elements/map"
23
+ require_relative "elements/timestamp"
24
+ require_relative "elements/duration"