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.
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Map < Literal
5
+ ALLOWED_TYPES = %i[int uint bool string].map { |typ| TYPES[typ] }.freeze
6
+
7
+ attr_reader :depth
8
+
9
+ def initialize(value, depth: 1)
10
+ # store array internally to support repeated keys
11
+ value = value.map do |k, v|
12
+ [Literal.to_cel_type(k), Literal.to_cel_type(v)]
13
+ end
14
+ super(TYPES[:map], value)
15
+ @depth = depth
16
+ end
17
+
18
+ def ==(other)
19
+ case other
20
+ when Map
21
+ @value.all? do |args|
22
+ other.value.include?(args)
23
+ end
24
+ when Array
25
+ # calls to != will downgrade maps to arrays
26
+ @value.all? do |args|
27
+ other.include?(args)
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ (LOGICAL_OPERATORS - %w[== != in]).each do |op|
35
+ @cel_methods.delete(op.to_sym)
36
+
37
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
38
+ def #{op}(*)
39
+ raise NoMethodError
40
+ end
41
+ OUT
42
+ end
43
+
44
+ def key?(key)
45
+ ukey =
46
+ case key
47
+ when Symbol
48
+ key.to_s
49
+ when String
50
+ key.delete_prefix('"').delete_suffix('"')
51
+ else
52
+ key
53
+ end
54
+
55
+ @value.any? do |k, _v|
56
+ k == ukey
57
+ end
58
+ end
59
+
60
+ alias_method :include?, :key?
61
+
62
+ define_cel_method(:[]) do |key|
63
+ ukey =
64
+ case key
65
+ when Symbol
66
+ key.to_s
67
+ when String
68
+ key.delete_prefix('"').delete_suffix('"')
69
+ else
70
+ key
71
+ end
72
+
73
+ values = @value.filter_map do |k, v|
74
+ v if k == ukey
75
+ end
76
+
77
+ raise NoSuchKeyError.new(self, key) if values.empty?
78
+
79
+ raise EvaluateError, "Failed with repeated key" if values.size > 1
80
+
81
+ values.first
82
+ end
83
+
84
+ def to_ruby_type
85
+ value.to_h { |args| args.map(&:to_ruby_type) }
86
+ end
87
+
88
+ def respond_to_missing?(meth, *args)
89
+ return true if super
90
+ return false unless @value
91
+
92
+ meth_s = meth.to_s
93
+
94
+ @value.any? { |k, _| k == meth_s }
95
+ end
96
+
97
+ def method_missing(meth, *args)
98
+ return super unless @value
99
+
100
+ meth_s = meth.to_s
101
+ values = @value.filter_map { |k, v| v if k == meth_s }
102
+
103
+ return super if values.empty?
104
+
105
+ values.first
106
+ end
107
+
108
+ private
109
+
110
+ # For a map, the entry keys are sub-expressions that must evaluate to values
111
+ # of an allowed type (int, uint, bool, or string)
112
+ def check
113
+ return if @value.all? do |key, _|
114
+ key.is_a?(Identifier) || ALLOWED_TYPES.include?(key.type)
115
+ end
116
+
117
+ raise CheckError, "#{self} is invalid (keys must be of an allowed type (int, uint, bool, or string)"
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Message
5
+ EMPTY_STRUCT = {}.freeze
6
+
7
+ attr_reader :name, :struct, :package, :depth
8
+
9
+ def initialize(name, struct, package: nil, depth: 1)
10
+ @struct = struct ? struct.transform_keys(&:to_sym) : EMPTY_STRUCT
11
+ @name = name
12
+ @package = package
13
+ @depth = depth
14
+ end
15
+
16
+ def type
17
+ @type ||= if proto_type
18
+ Protobuf.convert_to_cel_type(proto_type, @package) || proto_type
19
+ else
20
+ Protobuf.try_convert_from_wrapper_type(@name.to_s)
21
+ end
22
+ end
23
+
24
+ def message_type
25
+ @message_type ||= proto_type || container
26
+ end
27
+
28
+ def proto_type
29
+ @proto_type ||= Protobuf.convert_to_proto_type(@name.to_s, @package)
30
+ end
31
+
32
+ def ==(other)
33
+ other.is_a?(Cel::Message) && other.name == @name && other.struct == @struct
34
+ end
35
+
36
+ def to_s
37
+ if proto_type
38
+ proto_type.descriptor.name
39
+ else
40
+ message_type.to_s
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def container
47
+ @container ||= Cel.message_container(@name, @struct)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Null < Literal
5
+ def initialize
6
+ super(:null_type, nil)
7
+ end
8
+
9
+ INSTANCE = new.freeze
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Number < Literal
5
+ def ==(other)
6
+ case other
7
+ when Number
8
+ @value == other.value
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ ([:+, :-, *(MULTI_OPERATORS - %w[%])] - %i[%]).each do |op|
15
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
16
+ define_cel_method(:#{op}) do |other|
17
+ value = super(other)
18
+ check_overflow(value)
19
+ Number.new(@type, value)
20
+ end
21
+ OUT
22
+ end
23
+
24
+ (LOGICAL_OPERATORS - %w[== != in]).each do |op|
25
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
26
+ define_cel_method(:#{op}) do |other|
27
+ super(other)
28
+ end
29
+ OUT
30
+ end
31
+
32
+ define_cel_method(:%) do |other|
33
+ raise EvaluateError, "no matching overload" if @type == :double && other.type == :double
34
+
35
+ value = if negative?
36
+ if other.negative?
37
+ -(abs % other.abs)
38
+ else
39
+ -(abs % other)
40
+ end
41
+ else
42
+ @value % other.abs
43
+ end
44
+ check_overflow(value)
45
+ Number.new(@type, value)
46
+ end
47
+
48
+ %i[+ -].each do |op|
49
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
50
+ def #{op}@
51
+ raise NoOverloadError if @type == TYPES[:uint]
52
+ value = super
53
+ Number.new(@type, value)
54
+ end
55
+ OUT
56
+ end
57
+
58
+ def check_overflow(value = @value)
59
+ case @type
60
+ when TYPES[:double]
61
+ return if value.nan? || value.infinite?
62
+ raise EvaluateError, "return error for overflow" unless (-(MAX_FLOAT - 1)...MAX_FLOAT).cover?(value)
63
+ when TYPES[:uint]
64
+ raise EvaluateError, "return error for overflow" unless (0...MAX_INT).cover?(value)
65
+ else
66
+ raise EvaluateError, "return error for overflow" unless (-(MAX_INT - 1)...MAX_INT).cover?(value)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Operation
5
+ attr_reader :op, :operands, :depth
6
+
7
+ attr_accessor :type
8
+
9
+ def initialize(op, operands, depth: 1)
10
+ @op = op
11
+ @operands = operands
12
+ @type = TYPES[:any]
13
+ @depth = depth
14
+ end
15
+
16
+ def ==(other)
17
+ case other
18
+ when Array
19
+ other.size == @operands.size + 1 &&
20
+ other.first == @op &&
21
+ other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
22
+ when Operation
23
+ @op == other.op && @type == other.type && @operands == other.operands
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def unary?
30
+ @operands.size == 1
31
+ end
32
+
33
+ def to_s
34
+ return "#{@op}#{@operands.first}" if @operands.size == 1
35
+
36
+ @operands.join(" #{@op} ")
37
+ end
38
+ end
39
+ end
@@ -400,6 +400,10 @@ module Cel
400
400
  end
401
401
 
402
402
  module CelComparisonMode
403
+ # TODO: this does not isolate the scope of methods callable under cel.
404
+ # we'll need a different version of the CelMethods mixin for this.
405
+ alias_method :cel_send, :public_send
406
+
403
407
  def ==(other)
404
408
  super || begin
405
409
  return false unless other.is_a?(Protobuf.base_class)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class String < Literal
5
+ def initialize(value)
6
+ super(:string, value)
7
+ end
8
+
9
+ # override base String#%
10
+ @cel_methods.delete(:%)
11
+
12
+ def %(_other)
13
+ raise NoMethodError, "undefined method '%' for #{self}"
14
+ end
15
+
16
+ (LOGICAL_OPERATORS - %w[== != in]).each do |op|
17
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
18
+ define_cel_method(:#{op}) do |other|
19
+ super(other)
20
+ end
21
+ OUT
22
+ end
23
+
24
+ # CEL string functions
25
+
26
+ define_cel_method(:contains) do |string|
27
+ Bool.cast(@value.include?(string))
28
+ end
29
+
30
+ define_cel_method(:endsWith) do |string|
31
+ Bool.cast(@value.end_with?(string))
32
+ end
33
+
34
+ define_cel_method(:startsWith) do |string|
35
+ Bool.cast(@value.start_with?(string))
36
+ end
37
+
38
+ define_cel_method(:matches) do |pattern|
39
+ Macro.matches(self, pattern)
40
+ end
41
+
42
+ %i[+ -].each do |op|
43
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
44
+ define_cel_method(:#{op}) do |other|
45
+ String.new(super(other))
46
+ end
47
+ OUT
48
+ end
49
+
50
+ def to_s
51
+ inspect
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Cel
6
+ class Timestamp < Literal
7
+ TIMESTAMP_RANGE = (Time.parse("0001-01-01T00:00:00Z")..Time.parse("9999-12-31T23:59:59.999999999Z"))
8
+
9
+ def initialize(value)
10
+ value = case value
11
+ when ::String then Time.parse(value)
12
+ when Numeric then Time.at(value)
13
+ else value
14
+ end
15
+
16
+ raise EvaluateError, "out of range" unless TIMESTAMP_RANGE.include?(value)
17
+
18
+ super(:timestamp, value.round(9))
19
+ end
20
+
21
+ define_cel_method(:+) do |other|
22
+ Timestamp.new(@value + other.to_f)
23
+ end
24
+
25
+ define_cel_method(:-) do |other|
26
+ case other
27
+ when Timestamp
28
+ Duration.new(@value - other.value)
29
+ when Duration
30
+ Timestamp.new(@value - other.to_f)
31
+ end
32
+ end
33
+
34
+ LOGICAL_OPERATORS.each do |op|
35
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
36
+ define_cel_method(:#{op}) do |other|
37
+ val = super(other)
38
+ other.is_a?(Cel::Literal) ? Bool.cast(val) : val
39
+ end
40
+ OUT
41
+ end
42
+
43
+ # Cel Functions
44
+
45
+ define_cel_method(:getDate) do |tz = nil|
46
+ Number.new(:int, to_local_time(tz).day)
47
+ end
48
+
49
+ define_cel_method(:getDayOfMonth) do |tz = nil|
50
+ getDate(tz) - 1
51
+ end
52
+
53
+ define_cel_method(:getDayOfWeek) do |tz = nil|
54
+ Number.new(:int, to_local_time(tz).wday)
55
+ end
56
+
57
+ define_cel_method(:getDayOfYear) do |tz = nil|
58
+ Number.new(:int, to_local_time(tz).yday - 1)
59
+ end
60
+
61
+ define_cel_method(:getMonth) do |tz = nil|
62
+ Number.new(:int, to_local_time(tz).month - 1)
63
+ end
64
+
65
+ define_cel_method(:getFullYear) do |tz = nil|
66
+ Number.new(:int, to_local_time(tz).year)
67
+ end
68
+
69
+ define_cel_method(:getHours) do |tz = nil|
70
+ Number.new(:int, to_local_time(tz).hour)
71
+ end
72
+
73
+ define_cel_method(:getMinutes) do |tz = nil|
74
+ Number.new(:int, to_local_time(tz).min)
75
+ end
76
+
77
+ define_cel_method(:getSeconds) do |tz = nil|
78
+ Number.new(:int, to_local_time(tz).sec)
79
+ end
80
+
81
+ define_cel_method(:getMilliseconds) do |tz = nil|
82
+ Number.new(:int, to_local_time(tz).nsec / 1_000_000)
83
+ end
84
+
85
+ def to_ruby_type
86
+ Protobuf.timestamp_class.from_time(@value)
87
+ end
88
+
89
+ def to_s
90
+ @value.utc.iso8601
91
+ end
92
+
93
+ private
94
+
95
+ def to_local_time(tz = nil)
96
+ time = @value
97
+ if tz
98
+ tz = tz.value
99
+ if tz.match?(/\A[+-]?\d{2,}:\d{2,}\z/)
100
+ tz.prepend("+") unless tz.start_with?("+", "-")
101
+ else
102
+ tz = TZInfo::Timezone.get(tz)
103
+ end
104
+ time = time.getlocal(tz)
105
+ end
106
+ time
107
+ end
108
+ end
109
+ end