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.
@@ -1,846 +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 NoOverloadError 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
-
368
- def to_s
369
- inspect
370
- end
371
- end
372
-
373
- class Bytes < Literal
374
- attr_reader :string
375
-
376
- def initialize(value)
377
- super(:bytes, value)
378
- @string = value.pack("C*")
379
- end
380
-
381
- def to_ary
382
- [self]
383
- end
384
-
385
- alias_method :to_ruby_type, :string
386
-
387
- (LOGICAL_OPERATORS - %w[==]).each do |op|
388
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
389
- def #{op}(other)
390
- other.is_a?(Cel::Bytes) ? Bool.cast(@string.__send__(__method__, other.string)) : super
391
- end
392
- OUT
393
- end
394
-
395
- %i[+ -].each do |op|
396
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
397
- def #{op}(other)
398
- Bytes.new(@value + other.value)
399
- end
400
- OUT
401
- end
402
- end
403
-
404
- class List < Literal
405
- attr_reader :depth
406
-
407
- def initialize(value, depth: 1)
408
- value = value.map do |v|
409
- Literal.to_cel_type(v)
410
- end
411
- super(TYPES[:list], value)
412
- @depth = depth
413
- end
414
-
415
- def [](key)
416
- case key
417
- when Number
418
- if key.type == :double
419
- val = key.value
420
-
421
- raise InvalidArgumentError, key unless (val % 1).zero?
422
- end
423
- end
424
-
425
- super
426
- end
427
-
428
- def to_ary
429
- [self]
430
- end
431
-
432
- def to_ruby_type
433
- value.map(&:to_ruby_type)
434
- end
435
-
436
- %i[+ -].each do |op|
437
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
438
- def #{op}(other)
439
- List.new(@value.send(__method__, other.value))
440
- end
441
- OUT
442
- end
443
-
444
- def by_max_depth
445
- max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
446
-
447
- # return the last value if all options have the same depth
448
- return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
449
-
450
- max
451
- end
452
-
453
- def to_s
454
- "[#{@value.map(&:to_s).join(", ")}]"
455
- end
456
-
457
- private
458
-
459
- def calc_depth(element, acc)
460
- case element
461
- when List
462
- element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
463
- when Map
464
- element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
465
- else
466
- acc
467
- end
468
- end
469
- end
470
-
471
- class Map < Literal
472
- ALLOWED_TYPES = %i[int uint bool string].map { |typ| TYPES[typ] }.freeze
473
-
474
- attr_reader :depth
475
-
476
- def initialize(value, depth: 1)
477
- # store array internally to support repeated keys
478
- value = value.map do |k, v|
479
- [Literal.to_cel_type(k), Literal.to_cel_type(v)]
480
- end
481
- super(TYPES[:map], value)
482
- @depth = depth
483
- end
484
-
485
- def ==(other)
486
- case other
487
- when Map
488
- @value.all? do |args|
489
- other.value.include?(args)
490
- end
491
- when Array
492
- # calls to != will downgrade maps to arrays
493
- @value.all? do |args|
494
- other.include?(args)
495
- end
496
- else
497
- super
498
- end
499
- end
500
-
501
- (LOGICAL_OPERATORS - %w[== != in]).each do |op|
502
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
503
- def #{op}(*)
504
- raise NoMethodError
505
- end
506
- OUT
507
- end
508
-
509
- def key?(key)
510
- @value.any? { |k, _v| k == key }
511
- end
512
-
513
- alias_method :include?, :key?
514
-
515
- def [](key)
516
- values = @value.filter_map { |k, v| v if k == key }
517
-
518
- raise NoSuchKeyError.new(self, key) if values.empty?
519
-
520
- raise EvaluateError, "Failed with repeated key" if values.size > 1
521
-
522
- values.first
523
- end
524
-
525
- def to_ruby_type
526
- value.to_h { |args| args.map(&:to_ruby_type) }
527
- end
528
-
529
- def respond_to_missing?(meth, *args)
530
- return true if super
531
- return false unless @value
532
-
533
- meth_s = meth.to_s
534
-
535
- @value.any? { |k, _| k == meth_s }
536
- end
537
-
538
- def method_missing(meth, *args)
539
- return super unless @value
540
-
541
- meth_s = meth.to_s
542
- values = @value.filter_map { |k, v| v if k == meth_s }
543
-
544
- return super if values.empty?
545
-
546
- values.first
547
- end
548
-
549
- private
550
-
551
- # For a map, the entry keys are sub-expressions that must evaluate to values
552
- # of an allowed type (int, uint, bool, or string)
553
- def check
554
- return if @value.all? do |key, _|
555
- key.is_a?(Identifier) || ALLOWED_TYPES.include?(key.type)
556
- end
557
-
558
- raise CheckError, "#{self} is invalid (keys must be of an allowed type (int, uint, bool, or string)"
559
- end
560
- end
561
-
562
- class Timestamp < Literal
563
- TIMESTAMP_RANGE = (Time.parse("0001-01-01T00:00:00Z")..Time.parse("9999-12-31T23:59:59.999999999Z"))
564
-
565
- def initialize(value)
566
- value = case value
567
- when ::String then Time.parse(value)
568
- when Numeric then Time.at(value)
569
- else value
570
- end
571
-
572
- raise EvaluateError, "out of range" unless TIMESTAMP_RANGE.include?(value)
573
-
574
- super(:timestamp, value.round(9))
575
- end
576
-
577
- def +(other)
578
- Timestamp.new(@value + other.to_f)
579
- end
580
-
581
- def -(other)
582
- case other
583
- when Timestamp
584
- Duration.new(@value - other.value)
585
- when Duration
586
- Timestamp.new(@value - other.to_f)
587
- end
588
- end
589
-
590
- LOGICAL_OPERATORS.each do |op|
591
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
592
- def #{op}(other)
593
- other.is_a?(Cel::Literal) ? Bool.cast(super) : super
594
- end
595
- OUT
596
- end
597
-
598
- # Cel Functions
599
-
600
- def getDate(tz = nil)
601
- Number.new(:int, to_local_time(tz).day)
602
- end
603
-
604
- def getDayOfMonth(tz = nil)
605
- getDate(tz) - 1
606
- end
607
-
608
- def getDayOfWeek(tz = nil)
609
- Number.new(:int, to_local_time(tz).wday)
610
- end
611
-
612
- def getDayOfYear(tz = nil)
613
- Number.new(:int, to_local_time(tz).yday - 1)
614
- end
615
-
616
- def getMonth(tz = nil)
617
- Number.new(:int, to_local_time(tz).month - 1)
618
- end
619
-
620
- def getFullYear(tz = nil)
621
- Number.new(:int, to_local_time(tz).year)
622
- end
623
-
624
- def getHours(tz = nil)
625
- Number.new(:int, to_local_time(tz).hour)
626
- end
627
-
628
- def getMinutes(tz = nil)
629
- Number.new(:int, to_local_time(tz).min)
630
- end
631
-
632
- def getSeconds(tz = nil)
633
- Number.new(:int, to_local_time(tz).sec)
634
- end
635
-
636
- def getMilliseconds(tz = nil)
637
- Number.new(:int, to_local_time(tz).nsec / 1_000_000)
638
- end
639
-
640
- def to_ruby_type
641
- Protobuf.timestamp_class.from_time(@value)
642
- end
643
-
644
- def to_s
645
- @value.utc.iso8601
646
- end
647
-
648
- private
649
-
650
- def to_local_time(tz = nil)
651
- time = @value
652
- if tz
653
- tz = tz.value
654
- if tz.match?(/\A[+-]?\d{2,}:\d{2,}\z/)
655
- tz.prepend("+") unless tz.start_with?("+", "-")
656
- else
657
- tz = TZInfo::Timezone.get(tz)
658
- end
659
- time = time.getlocal(tz)
660
- end
661
- time
662
- end
663
- end
664
-
665
- class Duration < Literal
666
- def initialize(value)
667
- value =
668
- case value
669
- when ::String
670
- init_from_string(value)
671
- when Hash
672
- seconds, nanos = value.values_at(:seconds, :nanos)
673
- seconds ||= 0
674
- nanos ||= 0
675
- seconds + (nanos / 1_000_000_000.0)
676
- else
677
- value
678
- end
679
-
680
- raise EvaluateError, "out of range" unless (value * 1_000_000_000).between?(-MAX_INT, MAX_INT)
681
-
682
- super(:duration, value)
683
- end
684
-
685
- def to_s
686
- seconds = getSeconds
687
- millis = getMilliseconds
688
- millis.positive? ? "#{seconds}s#{millis}m" : "#{seconds}s"
689
- end
690
-
691
- def +(other)
692
- case other
693
- when Cel::Duration
694
- Cel::Duration.new(@value + other.value)
695
- when Cel::Timestamp
696
- Cel::Timestamp.new(other.value + @value)
697
- end
698
- end
699
-
700
- def -(other)
701
- case other
702
- when Cel::Duration
703
- Cel::Duration.new(@value - other.value)
704
- else
705
- raise Error, "invalid operand #{other} for `-`"
706
- end
707
- end
708
-
709
- LOGICAL_OPERATORS.each do |op|
710
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
711
- def #{op}(other)
712
- case other
713
- when Cel::Literal
714
- Bool.cast(super)
715
- when Numeric
716
- @value == other
717
-
718
- else
719
- super
720
- end
721
- end
722
- OUT
723
- end
724
-
725
- # Cel Functions
726
-
727
- def getHours
728
- Cel::Number.new(:int, (getMinutes / 60).to_i)
729
- end
730
-
731
- def getMinutes
732
- Cel::Number.new(:int, (getSeconds / 60).to_i)
733
- end
734
-
735
- def getSeconds
736
- Cel::Number.new(:int, @value.divmod(1).first)
737
- end
738
-
739
- def getMilliseconds
740
- Cel::Number.new(:int, (@value.divmod(1).last * 1000).round)
741
- end
742
-
743
- def to_ruby_type
744
- Protobuf.duration_class.new(seconds: getSeconds.value, nanos: getMilliseconds.value * 1_000_000_000.0)
745
- end
746
-
747
- private
748
-
749
- def init_from_string(value)
750
- seconds = 0
751
- nanos = 0
752
- value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
753
- case units
754
- when "h"
755
- seconds += Cel.to_numeric(duration) * 60 * 60
756
- when "m"
757
- seconds += Cel.to_numeric(duration) * 60
758
- when "s"
759
- seconds += Cel.to_numeric(duration)
760
- when "ms"
761
- nanos += Cel.to_numeric(duration) * 1000 * 1000
762
- when "us"
763
- nanos += Cel.to_numeric(duration) * 1000
764
- when "ns"
765
- nanos += Cel.to_numeric(duration)
766
- else
767
- raise EvaluateError, "#{units} is unsupported"
768
- end
769
- end
770
- duration = seconds + (nanos / 1_000_000_000.0)
771
- duration = -duration if value.start_with?("-")
772
- duration
773
- end
774
- end
775
-
776
- class Group
777
- attr_reader :value
778
-
779
- def initialize(value)
780
- @value = value
781
- end
782
-
783
- def type
784
- TYPES[:any]
785
- end
786
-
787
- def ==(other)
788
- other.is_a?(Group) && @value == other.value
789
- end
790
- end
791
-
792
- class Operation
793
- attr_reader :op, :operands, :depth
794
-
795
- attr_accessor :type
796
-
797
- def initialize(op, operands, depth: 1)
798
- @op = op
799
- @operands = operands
800
- @type = TYPES[:any]
801
- @depth = depth
802
- end
803
-
804
- def ==(other)
805
- case other
806
- when Array
807
- other.size == @operands.size + 1 &&
808
- other.first == @op &&
809
- other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
810
- when Operation
811
- @op == other.op && @type == other.type && @operands == other.operands
812
- else
813
- super
814
- end
815
- end
816
-
817
- def unary?
818
- @operands.size == 1
819
- end
820
-
821
- def to_s
822
- return "#{@op}#{@operands.first}" if @operands.size == 1
823
-
824
- @operands.join(" #{@op} ")
825
- end
826
- end
827
-
828
- class Condition
829
- attr_reader :if, :then, :else, :depth
830
-
831
- def initialize(if_, then_, else_, depth: 1)
832
- @if = if_
833
- @then = then_
834
- @else = else_
835
- @depth = depth
836
- end
837
-
838
- def type
839
- TYPES[:any]
840
- end
841
-
842
- def ==(other)
843
- other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
844
- end
845
- end
846
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"