cel 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e83a98ac5e076442ae06b53d13f43581a3982196f57582bbac02a08eef46ca9
4
- data.tar.gz: 1fdb06029d13f3a47037688df7d4d4a77f65e237aa73d0e00fb6989a78600000
3
+ metadata.gz: d6d9641e7b6b5cd40bda6d81519f89e9b5bd8e7d8d595306291e6d470ed3aa3c
4
+ data.tar.gz: 55490419080cc9eba65d907a99001deb7b3fbed08f1fc815e3dba293969162b8
5
5
  SHA512:
6
- metadata.gz: 59eb650749f3b0fcef9a8ee66b6850831db66f116d851f35894d375191e280cac465e48c1f39103d6fcb21f7ebcf6c695f2cd286c65bcfb6c12ef08ddcb07400
7
- data.tar.gz: b6dada73b82d27a17a6d9f23a672f36ec7c9af6f89eebef788227aba7b22cdc88720100ffcf5f466db70e923db689ff4df903942abb7ab2f8adeef70de4604df
6
+ metadata.gz: 222160a59e461b0b0b6166fc839edb760b8b696a935c0471cae0f3437d1049215777a613cf9840e4fbd2c85c91fc80798e142de963ad0523eff9e586c9a53bd2
7
+ data.tar.gz: 6abacc2f4aa0be7f74af0f44fec56bbfb57ccb770f60bb0072aa5334ed9381c7bb75447f948cf85278b40f42714f44139e9a3b6cfc6634e116ba6e821ea54961
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.1] - 2026-07-27
4
+
5
+ * parser: enabled support for declaring CEL messages inside a CEL list.
6
+ * protobuf: don't convert primitive types onto protobuf values if the result is from an unexpected type.
7
+ * protobuf: workaround internal conversions of `Google::Protobuf::Map` into a hash, due to its incompatible block-less implementation of `#to_h`.
8
+ * protobuf: fix initialization of a proto fied with repeated values.
9
+
10
+ ## [0.6.0] - 2026-02-28
11
+
12
+ ### Features
13
+
14
+ #### Network Extension
15
+
16
+ Support the network extension, as defined [here](https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#IP).
17
+
18
+ ### Improvements
19
+
20
+ #### support top-level lookup (ex: '.y')
21
+
22
+ also, make sure than, on lookup, keys under the passed container take priority, except in the case where the lookup is top-level
23
+ (i.e. '.y').
24
+
25
+ at the same time, #merge is refactored in order to:
26
+
27
+ * normalize nested bindings into aggregated keys ({a:{b:1}} => {:"a.b"=> 1}
28
+ * override container-prefixed variables with the local binding
29
+ * deal with conflicts by moving them to top level.
30
+
3
31
  ## [0.5.0] - 2025-12-11
4
32
 
5
33
  ### Features
data/README.md CHANGED
@@ -200,6 +200,7 @@ env.evaluate("ext.random()") #=> 42
200
200
  * bindings extensions
201
201
  * block extensions
202
202
  * encoders extensions
203
+ * network extensions
203
204
  * comprehensions V2 API
204
205
  * optionals
205
206
 
@@ -38,10 +38,10 @@ module Cel
38
38
  if indexing?
39
39
  "#{var}[#{args}]"
40
40
  else
41
- "#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
41
+ "#{var}.#{func}#{"(#{args.join(", ")})" if args}"
42
42
  end
43
43
  else
44
- "#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
44
+ "#{func}#{"(#{args.join(", ")})" if args}"
45
45
  end
46
46
  end
47
47
 
@@ -51,7 +51,7 @@ module Cel
51
51
  end
52
52
 
53
53
  def to_s
54
- "[#{@value.map(&:to_s).join(", ")}]"
54
+ "[#{@value.map(&:to_s).join(", ")}]" # rubocop:disable Style/MapJoin
55
55
  end
56
56
 
57
57
  private
@@ -16,7 +16,7 @@ module Cel
16
16
  end
17
17
 
18
18
  case val
19
- when Literal, Identifier, Invoke, Operation, Condition, Group, # already cel
19
+ when Literal, Message, Identifier, Invoke, Operation, Condition, Group, # already cel
20
20
  Protobuf.enum_class # already a usable class a la protobuf
21
21
  val
22
22
  when Protobuf.map_class
@@ -282,13 +282,39 @@ module Cel
282
282
 
283
283
  next([key, value]) unless field_subtype
284
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
- ]
285
+ if field_descriptor.label == :repeated
286
+ value = case value
287
+ when Hash
288
+ key_type, value_type = field_subtype.msgclass.descriptor.map(&:subtype)
289
+ value.filter_map do |k, v|
290
+ kconv = key_type && !k.is_a?(key_type.msgclass) ?
291
+ convert_to_proto(key_type.msgclass, k, convert_value_to_proto: true) :
292
+ k
293
+ vconv = value_type && !v.is_a?(value_type.msgclass) ?
294
+ convert_to_proto(value_type.msgclass, v, convert_value_to_proto: true) :
295
+ v
296
+
297
+ next unless vconv
298
+
299
+ [kconv, vconv]
300
+ end.to_h
301
+ else
302
+ # Array
303
+ value.filter_map do |v|
304
+ if v.is_a?(field_subtype.msgclass)
305
+ v
306
+ else
307
+ convert_to_proto(field_subtype.msgclass, v, convert_value_to_proto: true)
308
+ end
309
+ end
310
+ end
311
+ else
312
+ unless value.is_a?(field_subtype.msgclass)
313
+ value = convert_to_proto(field_subtype.msgclass, value, convert_value_to_proto: true)
314
+ end
315
+ end
316
+
317
+ [key, value]
292
318
  end
293
319
 
294
320
  # protobuf sets maps as abstract classes, and there's apparently no way to identify them
@@ -298,7 +324,11 @@ module Cel
298
324
 
299
325
  proto_type.new(values)
300
326
  elsif values.nil?
301
- Google::Protobuf::Value.from_ruby(values)
327
+ v = Google::Protobuf::Value.from_ruby(values)
328
+
329
+ return if proto_type && !v.is_a?(proto_type)
330
+
331
+ v
302
332
  else
303
333
  proto_type.new(value: values)
304
334
  end
@@ -318,7 +348,7 @@ module Cel
318
348
  protoclass = var.split(".").last
319
349
  protoclass = Google::Protobuf.const_get(protoclass)
320
350
 
321
- value = if args.nil? && protoclass.constants.include?(func.to_sym)
351
+ value = if args.nil? && protoclass.const_defined?(func.to_sym)
322
352
  protoclass.const_get(func)
323
353
  else
324
354
  protoclass.__send__(func, *args)
@@ -340,7 +370,7 @@ module Cel
340
370
  when :repeated
341
371
  case value
342
372
  when Google::Protobuf::Map
343
- value.to_h { |k, v| [k, convert_from_proto_field_type(field, v)] } # rubocop:disable Style/HashTransformValues
373
+ value.map { |k, v| [k, convert_from_proto_field_type(field, v)] }.to_h
344
374
  else
345
375
  value.map { |v| convert_from_proto_field_type(field, v) }
346
376
  end
data/lib/cel/ast/types.rb CHANGED
@@ -107,7 +107,7 @@ module Cel
107
107
  when :duration
108
108
  String.new(value.to_s)
109
109
  else
110
- String.new(String(value.value))
110
+ String.new(String(value))
111
111
  end
112
112
  when :bytes
113
113
  case type
data/lib/cel/checker.rb CHANGED
@@ -11,14 +11,14 @@ module Cel
11
11
  BOOLABLE_OPERATORS = %w[&& || == != < <= >= >].freeze
12
12
 
13
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],
14
+ int: %i[int uint double string timestamp].freeze, # TODO: enum
15
+ uint: %i[int uint double string].freeze,
16
+ string: %i[int uint double bytes bool bytes string timestamp duration].freeze,
17
+ double: %i[double int uint string].freeze,
18
+ bytes: %i[bytes string].freeze,
19
+ bool: %i[bool string].freeze,
20
+ duration: %i[string duration].freeze,
21
+ timestamp: %i[int string timestamp].freeze,
22
22
  }.freeze
23
23
 
24
24
  attr_reader :environment, :declarations
@@ -26,6 +26,15 @@ module Cel
26
26
  def initialize(environment, declarations = environment.declarations)
27
27
  @environment = environment
28
28
  @declarations = declarations
29
+ @cast_allowed_types = CAST_ALLOWED_TYPES.dup
30
+ @environment.extensions.each_value do |ext|
31
+ next unless ext.const_defined?(:CAST_ALLOWED_TYPES)
32
+
33
+ ext.const_get(:CAST_ALLOWED_TYPES).each do |type, allowed_types|
34
+ @cast_allowed_types[type] ||= []
35
+ @cast_allowed_types[type] += allowed_types
36
+ end
37
+ end
29
38
  end
30
39
 
31
40
  def check(ast)
@@ -66,6 +75,8 @@ module Cel
66
75
  end
67
76
 
68
77
  def check_arity(func, args, arity, op = :===)
78
+ return arity.zero? if args.nil?
79
+
69
80
  return if arity.__send__(op, args.size)
70
81
 
71
82
  raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
@@ -388,6 +399,12 @@ module Cel
388
399
  func = funcall.func
389
400
  args = funcall.args
390
401
 
402
+ @environment.implicit_extensions.each do |ext|
403
+ if (typ = ext.__check(funcall, checker: self))
404
+ return typ
405
+ end
406
+ end
407
+
391
408
  # check if protobuf conversion
392
409
  if (proto_type = Protobuf.convert_to_proto_type(func.to_s, @environment.package))
393
410
  # TODO: fixit
@@ -413,9 +430,9 @@ module Cel
413
430
 
414
431
  arg = call(args.first)
415
432
  return TYPES[:int] if find_match_all_types(%i[string bytes list map], arg)
416
- when *CAST_ALLOWED_TYPES.keys
433
+ when *@cast_allowed_types.keys
417
434
  check_arity(func, args, 1)
418
- allowed_types = CAST_ALLOWED_TYPES[func]
435
+ allowed_types = @cast_allowed_types[func]
419
436
 
420
437
  arg = call(args.first)
421
438
 
data/lib/cel/context.rb CHANGED
@@ -16,12 +16,46 @@ module Cel
16
16
  def lookup(identifier)
17
17
  raise EvaluateError, "no value in context for #{identifier}" unless @bindings
18
18
 
19
+ top_level_key = nil
19
20
  id = identifier.to_s
20
21
 
21
22
  lookup_keys = id.split(".")
22
23
 
23
24
  val = @bindings
24
25
 
26
+ if lookup_keys.first.empty?
27
+ # key starts with ".", i.e. ".y"
28
+ val = val.dup
29
+ lookup_keys.shift
30
+ top_level_key = key = lookup_keys.first
31
+
32
+ if val.respond_to?(:key?)
33
+ val = val.dup
34
+ val.keys.select { |k| k.start_with?(".#{key}") }.each do |k|
35
+ val[k.to_s[1..-1].to_sym] = val.delete(k) # rubocop:disable Style/SlicingWithRange
36
+ end
37
+ # else do nothing
38
+ end
39
+ end
40
+
41
+ if (container = @environment.container)
42
+ # ex move data from "example.com.y" to "y"
43
+ container_keys = val.keys.select { |k| k.start_with?(container) }
44
+
45
+ unless container_keys.empty?
46
+ val = val.dup
47
+ container_keys.each do |key|
48
+ skey = key.to_s.delete_prefix("#{container}.")
49
+
50
+ # forego editing if this is a top level lookup and the key being
51
+ # overwritten would be one
52
+ next if top_level_key && skey.start_with?(top_level_key)
53
+
54
+ val[skey.to_sym] = val.delete(key)
55
+ end
56
+ end
57
+ end
58
+
25
59
  loop do
26
60
  fetched = false
27
61
  (0...lookup_keys.size).reverse_each do |idx|
@@ -66,8 +100,38 @@ module Cel
66
100
  [val, lookup_keys.empty? ? nil : lookup_keys.join(".")]
67
101
  end
68
102
 
103
+ NORMALIZE_KEYS = lambda do |k, v|
104
+ if v.is_a?(Hash) || v.is_a?(Map)
105
+ v.flat_map do |k2, v2|
106
+ NORMALIZE_KEYS.call(:"#{k}.#{String(k2)}", v2)
107
+ end
108
+ else
109
+ [k, v]
110
+ end
111
+ end
112
+
69
113
  def merge(bindings)
70
- Context.new(@environment, @bindings ? @bindings.merge(bindings) : bindings)
114
+ bindings = bindings.to_h { |k, v| NORMALIZE_KEYS[k, v] }
115
+ if (old_bindings = @bindings)
116
+ if @environment.container
117
+ old_bindings = old_bindings.dup
118
+
119
+ bindings.each_key do |key|
120
+ container_key = :"#{@environment.container}.#{key}"
121
+
122
+ old_bindings.delete(container_key) if old_bindings.key?(container_key)
123
+ end
124
+ end
125
+ conflicts = []
126
+ bindings = old_bindings.merge(bindings) do |k, old, new|
127
+ conflicts << [k, old]
128
+ new
129
+ end
130
+ conflicts.each do |k, v|
131
+ bindings[:".#{k}"] = v
132
+ end
133
+ end
134
+ Context.new(@environment, bindings)
71
135
  end
72
136
 
73
137
  private
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cel
4
4
  class Environment
5
- attr_reader :declarations, :extensions, :package
5
+ attr_reader :declarations, :extensions, :package, :container
6
6
 
7
7
  def initialize(
8
8
  declarations: nil,
@@ -13,11 +13,12 @@ module Cel
13
13
  )
14
14
  @disable_check = disable_check
15
15
  @declarations = declarations
16
- @container = container
17
16
  @extensions = extensions ? EXTENSIONS.merge(extensions) : EXTENSIONS
18
- @package = nil
17
+ @package = @container = nil
19
18
  @checker = Checker.new(self)
20
19
 
20
+ @container = container if container && !container.empty?
21
+
21
22
  if container && !container.empty?
22
23
 
23
24
  module_dir = container.split(".")
@@ -38,6 +39,10 @@ module Cel
38
39
  @parser = Parser.new(package, **kwargs)
39
40
  end
40
41
 
42
+ def implicit_extensions
43
+ @implicit_extensions ||= @extensions.select { |_, ext| ext.implicit? }.values
44
+ end
45
+
41
46
  def compile(expr)
42
47
  ast = @parser.parse(expr)
43
48
  @checker.check(ast)
@@ -88,8 +93,10 @@ module Cel
88
93
  cbinding_keys.each do |k|
89
94
  cbinding = k.to_s.delete_prefix(prefix).to_sym
90
95
 
91
- bindings[cbinding] = bindings.delete(k)
92
- declarations[cbinding] = declarations.delete(k) if declarations.key?(k)
96
+ unless bindings.key?(cbinding)
97
+ bindings[cbinding] = bindings.delete(k)
98
+ declarations[cbinding] = declarations.delete(k) if declarations.key?(k)
99
+ end
93
100
  end
94
101
 
95
102
  [declarations, bindings]
@@ -5,6 +5,10 @@ module Cel
5
5
  module Bind
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  func = funcall.func
10
14
  args = funcall.args
@@ -6,6 +6,10 @@ module Cel
6
6
  module Base64
7
7
  module_function
8
8
 
9
+ def implicit?
10
+ false
11
+ end
12
+
9
13
  def __check(funcall, checker:)
10
14
  func = funcall.func
11
15
  args = funcall.args
@@ -5,6 +5,10 @@ module Cel
5
5
  module Math
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  func = funcall.func
10
14
  args = funcall.args
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+ require "ipaddr"
5
+
6
+ module Cel
7
+ module Extensions
8
+ module Network
9
+ module_function
10
+
11
+ def implicit?
12
+ false
13
+ end
14
+
15
+ def __check(funcall, checker:)
16
+ funcall.var
17
+ func = funcall.func
18
+ args = funcall.args
19
+
20
+ case func
21
+ when :IP, :CIDR
22
+ checker.check_arity(func, args, 0)
23
+ TYPES[:type]
24
+ end
25
+ end
26
+
27
+ def IP(*) # rubocop:disable Naming/MethodName
28
+ TYPES[:ip]
29
+ end
30
+
31
+ def CIDR(*) # rubocop:disable Naming/MethodName
32
+ TYPES[:cidr]
33
+ end
34
+
35
+ module IP
36
+ module_function
37
+
38
+ CAST_ALLOWED_TYPES = {
39
+ string: %i[ip cidr].freeze,
40
+ }.freeze
41
+
42
+ def implicit?
43
+ true
44
+ end
45
+
46
+ def __check(funcall, checker:)
47
+ funcall.var
48
+ func = funcall.func
49
+ args = funcall.args
50
+
51
+ case func
52
+ when :ip, :cidr
53
+ checker.check_arity(func, args, 1)
54
+ arg = checker.call(args.first)
55
+ TYPES[func] if checker.find_match_all_types(%i[string], arg)
56
+ when :isIP, :isCanonical
57
+ checker.check_arity(func, args, 1)
58
+ arg = checker.call(args.first)
59
+ TYPES[:bool] if checker.find_match_all_types(%i[string], arg)
60
+ end
61
+ end
62
+
63
+ def __ip(value)
64
+ Cel::IP.new(value)
65
+ rescue IPAddr::InvalidAddressError
66
+ raise EvaluateError, "IP Address '#{value}' parse error during conversion from string"
67
+ end
68
+
69
+ def __cidr(value)
70
+ Cel::CIDR.new(value)
71
+ rescue IPAddr::InvalidAddressError
72
+ raise EvaluateError, "IP Address '#{value}' parse error during conversion from string"
73
+ end
74
+
75
+ def ip(str, program:)
76
+ value = program.call(str).value
77
+
78
+ __ip(value)
79
+ end
80
+
81
+ def cidr(str, program:)
82
+ value = program.call(str).value
83
+
84
+ __cidr(value)
85
+ end
86
+
87
+ def isIP(str, program:) # rubocop:disable Naming/MethodName
88
+ value = program.call(str).value
89
+
90
+ begin
91
+ __ip(value)
92
+ Bool.new(true)
93
+ rescue EvaluateError
94
+ Bool.new(false)
95
+ end
96
+ end
97
+
98
+ def isCanonical(str, program:) # rubocop:disable Naming/MethodName
99
+ value = program.call(str).value
100
+
101
+ Bool.new(value == __ip(value).to_s)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#IP
108
+ class IP < Literal
109
+ IPV4_LL_UC_CIDR = IPAddr.new("169.254.0.0/16")
110
+ IPV4_LL_MC_CIDR = IPAddr.new("224.0.0.0/24")
111
+ IPV6_LL_UC_CIDR = IPAddr.new("fe80::/10")
112
+ IPV6_LL_MC_CIDR = IPAddr.new("ff00::/8")
113
+
114
+ def initialize(val)
115
+ value = IPAddr.new(val)
116
+
117
+ if value.ipv6?
118
+ raise EvaluateError, "IP Address with zone value is not allowed" if value.zone_id
119
+
120
+ if Resolv::IPv4::Regex.match?(val.split(":").last)
121
+ raise EvaluateError, "IPv4-mapped IPv6 address is not allowed"
122
+ end
123
+ end
124
+
125
+ super(:ip, value)
126
+ end
127
+
128
+ define_cel_method(:==) do |other|
129
+ case other
130
+ when IP
131
+ if @value.ipv4? && other.value.ipv6? && other.value.ipv4_mapped?
132
+ # convert ipv4 to ipv4-mapped ipv6 addr
133
+ @value.ipv4_mapped == other.value
134
+ elsif @value.ipv6? && other.value.ipv4? && @value.ipv4_mapped?
135
+ # convert ipv4 to ipv4-mapped ipv6 addr
136
+ @value == other.value.ipv4_mapped
137
+ else
138
+ # same family?
139
+ @value == other.value
140
+ end
141
+ else
142
+ super
143
+ end
144
+ end
145
+
146
+ define_cel_method(:family) do
147
+ Number.new(:int, @value.ipv6? ? 6 : 4)
148
+ end
149
+
150
+ define_cel_method(:isUnspecified) do
151
+ Bool.cast(@value == (@value.ipv6? ? "::" : "0.0.0.0"))
152
+ end
153
+
154
+ define_cel_method(:isLoopback) do
155
+ Bool.cast(@value.loopback?)
156
+ end
157
+
158
+ define_cel_method(:isGlobalUnicast) do
159
+ if @value.ipv6?
160
+ # not a link-local unicast, loopback or multicast address.
161
+ Bool.cast(!@value.loopback? && !IPV6_LL_UC_CIDR.include?(@value) && !IPV6_LL_MC_CIDR.include?(@value))
162
+ else
163
+ # not zero or 255.255.255.255
164
+ Bool.cast(@value != "255.255.255.255" && @value != "0.0.0.0")
165
+ end
166
+ end
167
+
168
+ define_cel_method(:isLinkLocalMulticast) do
169
+ Bool.cast(@value.ipv6? ? IPV6_LL_MC_CIDR.include?(@value) : IPV4_LL_MC_CIDR.include?(@value))
170
+ end
171
+
172
+ define_cel_method(:isLinkLocalUnicast) do
173
+ Bool.cast(@value.ipv6? ? IPV6_LL_UC_CIDR.include?(@value) : IPV4_LL_UC_CIDR.include?(@value))
174
+ end
175
+ end
176
+
177
+ class CIDR < Literal
178
+ def initialize(val)
179
+ value = IPAddr.new(val)
180
+
181
+ if value.ipv6?
182
+ raise EvaluateError, "IP Address with zone value is not allowed" if value.zone_id
183
+
184
+ val, = val.split("/", 2)
185
+ if Resolv::IPv4::Regex.match?(val.split(":").last)
186
+ raise EvaluateError, "IPv4-mapped IPv6 address is not allowed"
187
+ end
188
+ end
189
+
190
+ super(:cidr, value)
191
+ end
192
+
193
+ define_cel_method(:containsIP) do |other|
194
+ Bool.cast(@value.include?(other.value))
195
+ end
196
+
197
+ define_cel_method(:containsCIDR) do |other|
198
+ Bool.cast(@value.include?(other.value))
199
+ end
200
+
201
+ define_cel_method(:ip) do
202
+ IP.new(@value.to_s)
203
+ end
204
+
205
+ define_cel_method(:masked) do
206
+ self
207
+ end
208
+
209
+ define_cel_method(:prefixLength) do
210
+ Number.new(:int, @value.prefix)
211
+ end
212
+
213
+ def to_str
214
+ @value.cidr
215
+ end
216
+ end
217
+
218
+ TYPES[:ip] = Type.new(:ip)
219
+ TYPES[:cidr] = Type.new(:cidr)
220
+ end
@@ -5,6 +5,10 @@ module Cel
5
5
  module String
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  var = funcall.var
10
14
  func = funcall.func
@@ -4,6 +4,7 @@ require_relative "extensions/math"
4
4
  require_relative "extensions/string"
5
5
  require_relative "extensions/bind"
6
6
  require_relative "extensions/encoders"
7
+ require_relative "extensions/network"
7
8
 
8
9
  module Cel
9
10
  EXTENSIONS = {
@@ -11,5 +12,7 @@ module Cel
11
12
  strings: Extensions::String,
12
13
  base64: Extensions::Encoders::Base64,
13
14
  cel: Extensions::Bind,
15
+ net: Extensions::Network,
16
+ ip: Extensions::Network::IP,
14
17
  }.freeze
15
18
  end
data/lib/cel/parser.rb CHANGED
@@ -995,7 +995,7 @@ module_eval(<<'.,.,', 'parser.ry', 58)
995
995
 
996
996
  module_eval(<<'.,.,', 'parser.ry', 62)
997
997
  def _reduce_36(val, _values, result)
998
- result = val[1]
998
+ result = ".#{val[1]}"
999
999
  result
1000
1000
  end
1001
1001
  .,.,
data/lib/cel/program.rb CHANGED
@@ -76,6 +76,8 @@ module Cel
76
76
  case val
77
77
  when List
78
78
  List.new(val.value.map(&method(:call)))
79
+ when Map
80
+ Map.new(val.value.map { |k, v| [k, call(v)] }, depth: val.depth)
79
81
  else
80
82
  val
81
83
  end
@@ -208,10 +210,14 @@ module Cel
208
210
  return evaluate_invoke(invoke, proto_type)
209
211
  end
210
212
 
211
- # try stuff like a.b.c
212
- return evaluate_identifier(invoke.to_s) if args.nil?
213
-
214
- evaluate_identifier(var)
213
+ if @environment.extensions.key?(var.to_sym)
214
+ var = @environment.extensions[var.to_sym]
215
+ elsif args.nil?
216
+ # try stuff like a.b.c
217
+ return evaluate_identifier(invoke.to_s)
218
+ else
219
+ evaluate_identifier(var)
220
+ end
215
221
 
216
222
  when Invoke
217
223
  if args.nil?
@@ -265,7 +271,7 @@ module Cel
265
271
  raise EvaluateError, "#{invoke} is not supported" unless var.class.method_defined?(func, false)
266
272
 
267
273
  # eliminate protobuf API from the lookup
268
- raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.instance_methods.include?(func)
274
+ raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.method_defined?(func)
269
275
 
270
276
  # If e evaluates to a message and f is not declared in this message, the
271
277
  # runtime error no_such_field is raised.
@@ -274,16 +280,18 @@ module Cel
274
280
  var.cel_send(func, *args)
275
281
  when Literal
276
282
  # eliminate ruby API from the lookup
277
- raise NoMatchingOverloadError.new(var, func) if Literal.instance_methods.include?(func)
283
+ raise NoMatchingOverloadError.new(var, func) if Literal.method_defined?(func)
278
284
 
279
285
  # If e evaluates to a message and f is not declared in this message, the
280
286
  # runtime error no_such_field is raised.
281
287
  raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)
282
288
 
289
+ args ?
290
+ var.cel_send(func, *Array(args).map(&method(:call))) :
283
291
  var.cel_send(func)
284
292
  when Protobuf.base_class
285
293
  # eliminate protobuf API from the lookup
286
- raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.instance_methods.include?(func)
294
+ raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.method_defined?(func)
287
295
 
288
296
  # If e evaluates to a message and f is not declared in this message, the
289
297
  # runtime error no_such_field is raised.
@@ -344,6 +352,12 @@ module Cel
344
352
  func = funcall.func
345
353
  args = funcall.args
346
354
 
355
+ unless Module.respond_to?(func)
356
+ @environment.implicit_extensions.each do |ext|
357
+ return ext.__send__(func, *args, program: self) if ext.respond_to?(func)
358
+ end
359
+ end
360
+
347
361
  case func
348
362
  when :type
349
363
  operand = args.first
data/lib/cel/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cel
4
4
  module Ruby
5
- VERSION = "0.5.0"
5
+ VERSION = "0.6.1"
6
6
  end
7
7
  end
data/lib/cel.rb CHANGED
@@ -22,8 +22,7 @@ end
22
22
 
23
23
  begin
24
24
  require_relative "cel/ast/elements/protobuf"
25
- rescue LoadError => e
26
- puts e
25
+ rescue LoadError
27
26
  module Cel
28
27
  class << self
29
28
  # returns a struct class containing the attributes that one would
@@ -114,26 +113,27 @@ rescue LoadError => e
114
113
  module Protobuf
115
114
  module_function
116
115
 
117
- BASE_CLASS = Class.new(Object)
116
+ class BaseClass < Object
117
+ end
118
118
 
119
119
  def base_class
120
120
  Struct
121
121
  end
122
122
 
123
123
  def enum_class
124
- BASE_CLASS
124
+ BaseClass
125
125
  end
126
126
 
127
127
  def map_class
128
- BASE_CLASS
128
+ BaseClass
129
129
  end
130
130
 
131
131
  def timestamp_class
132
- BASE_CLASS
132
+ BaseClass
133
133
  end
134
134
 
135
135
  def duration_class
136
- BASE_CLASS
136
+ BaseClass
137
137
  end
138
138
 
139
139
  def convert_to_proto(message_type, values)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -109,6 +109,7 @@ files:
109
109
  - lib/cel/extensions/bind.rb
110
110
  - lib/cel/extensions/encoders.rb
111
111
  - lib/cel/extensions/math.rb
112
+ - lib/cel/extensions/network.rb
112
113
  - lib/cel/extensions/string.rb
113
114
  - lib/cel/macro.rb
114
115
  - lib/cel/parser.rb