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 +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +1 -0
- data/lib/cel/ast/elements/invoke.rb +2 -2
- data/lib/cel/ast/elements/list.rb +1 -1
- data/lib/cel/ast/elements/literal.rb +1 -1
- data/lib/cel/ast/elements/protobuf.rb +40 -10
- data/lib/cel/ast/types.rb +1 -1
- data/lib/cel/checker.rb +27 -10
- data/lib/cel/context.rb +65 -1
- data/lib/cel/environment.rb +12 -5
- data/lib/cel/extensions/bind.rb +4 -0
- data/lib/cel/extensions/encoders.rb +4 -0
- data/lib/cel/extensions/math.rb +4 -0
- data/lib/cel/extensions/network.rb +220 -0
- data/lib/cel/extensions/string.rb +4 -0
- data/lib/cel/extensions.rb +3 -0
- data/lib/cel/parser.rb +1 -1
- data/lib/cel/program.rb +21 -7
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +7 -7
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6d9641e7b6b5cd40bda6d81519f89e9b5bd8e7d8d595306291e6d470ed3aa3c
|
|
4
|
+
data.tar.gz: 55490419080cc9eba65d907a99001deb7b3fbed08f1fc815e3dba293969162b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
@@ -38,10 +38,10 @@ module Cel
|
|
|
38
38
|
if indexing?
|
|
39
39
|
"#{var}[#{args}]"
|
|
40
40
|
else
|
|
41
|
-
"#{var}.#{func}#{"(#{args.
|
|
41
|
+
"#{var}.#{func}#{"(#{args.join(", ")})" if args}"
|
|
42
42
|
end
|
|
43
43
|
else
|
|
44
|
-
"#{func}#{"(#{args.
|
|
44
|
+
"#{func}#{"(#{args.join(", ")})" if args}"
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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.
|
|
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.
|
|
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
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
|
|
433
|
+
when *@cast_allowed_types.keys
|
|
417
434
|
check_arity(func, args, 1)
|
|
418
|
-
allowed_types =
|
|
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
|
-
|
|
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
|
data/lib/cel/environment.rb
CHANGED
|
@@ -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
|
-
|
|
92
|
-
|
|
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]
|
data/lib/cel/extensions/bind.rb
CHANGED
data/lib/cel/extensions/math.rb
CHANGED
|
@@ -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
|
data/lib/cel/extensions.rb
CHANGED
|
@@ -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
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
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
|
|
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
|
-
|
|
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
|
-
|
|
124
|
+
BaseClass
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def map_class
|
|
128
|
-
|
|
128
|
+
BaseClass
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
def timestamp_class
|
|
132
|
-
|
|
132
|
+
BaseClass
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
def duration_class
|
|
136
|
-
|
|
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.
|
|
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
|