cel 0.2.3 → 0.3.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.
@@ -2,10 +2,38 @@
2
2
 
3
3
  module Cel
4
4
  class Environment
5
- def initialize(declarations = nil)
5
+ attr_reader :declarations, :package
6
+
7
+ def initialize(
8
+ declarations: nil,
9
+ container: nil,
10
+ disable_check: false,
11
+ **kwargs
12
+ )
13
+ @disable_check = disable_check
6
14
  @declarations = declarations
7
- @parser = Parser.new
8
- @checker = Checker.new(@declarations)
15
+ @container = container
16
+ @package = nil
17
+ @checker = Checker.new(self)
18
+
19
+ if container && !container.empty?
20
+
21
+ module_dir = container.split(".")
22
+
23
+ Dir[File.join(*module_dir, "*.rb")].each do |path|
24
+ require path
25
+ end
26
+
27
+ proto_module_name = container.split(".").map do |sub|
28
+ sub.capitalize.gsub(/_([a-z\d]*)/) do
29
+ ::Regexp.last_match(1).capitalize
30
+ end
31
+ end.join("::")
32
+
33
+ @package = Object.const_get(proto_module_name) if Object.const_defined?(proto_module_name)
34
+ end
35
+
36
+ @parser = Parser.new(package, **kwargs)
9
37
  end
10
38
 
11
39
  def compile(expr)
@@ -20,7 +48,7 @@ module Cel
20
48
 
21
49
  def decode(encoded_expr)
22
50
  ast = Encoder.decode(encoded_expr)
23
- @checker.check(ast)
51
+ @checker.check(ast) unless @disable_check
24
52
  ast
25
53
  end
26
54
 
@@ -31,15 +59,36 @@ module Cel
31
59
 
32
60
  def program(expr)
33
61
  expr = @parser.parse(expr) if expr.is_a?(::String)
34
- @checker.check(expr)
35
- Runner.new(@declarations, expr)
62
+ @checker.check(expr) unless @disable_check
63
+ Runner.new(self, expr)
36
64
  end
37
65
 
38
66
  def evaluate(expr, bindings = nil)
39
- context = Context.new(@declarations, bindings)
67
+ declarations, bindings = process_bindings(bindings)
68
+ context = Context.new(declarations, bindings)
40
69
  expr = @parser.parse(expr) if expr.is_a?(::String)
41
- @checker.check(expr)
42
- Program.new(context).evaluate(expr)
70
+ @checker.check(expr) unless @disable_check
71
+ Program.new(context, self).evaluate(expr)
72
+ end
73
+
74
+ def process_bindings(bindings)
75
+ return [@declarations, bindings] unless @container && @package.nil?
76
+
77
+ declarations = @declarations.dup
78
+ bindings = bindings.dup
79
+
80
+ prefix = "#{@container}."
81
+
82
+ cbinding_keys = bindings.keys.select { |k| k.start_with?(prefix) }
83
+
84
+ cbinding_keys.each do |k|
85
+ cbinding = k.to_s.delete_prefix(prefix).to_sym
86
+
87
+ bindings[cbinding] = bindings.delete(k)
88
+ declarations[cbinding] = declarations.delete(k) if declarations.key?(k)
89
+ end
90
+
91
+ [declarations, bindings]
43
92
  end
44
93
 
45
94
  private
@@ -48,14 +97,15 @@ module Cel
48
97
  end
49
98
 
50
99
  class Runner
51
- def initialize(declarations, ast)
52
- @declarations = declarations
100
+ def initialize(environment, ast)
101
+ @environment = environment
53
102
  @ast = ast
54
103
  end
55
104
 
56
105
  def evaluate(bindings = nil)
57
- context = Context.new(@declarations, bindings)
58
- Program.new(context).evaluate(@ast)
106
+ declarations, bindings = @environment.process_bindings(bindings)
107
+ context = Context.new(declarations, bindings)
108
+ Program.new(context, @environment).evaluate(@ast)
59
109
  end
60
110
  end
61
111
  end
data/lib/cel/errors.rb CHANGED
@@ -5,10 +5,31 @@ module Cel
5
5
 
6
6
  class ParseError < Error; end
7
7
 
8
+ class MaxRecursionDepthExceededError < ParseError; end
9
+ class MaxNestingDepthExceededError < ParseError; end
10
+
8
11
  class CheckError < Error; end
9
12
 
10
13
  class EvaluateError < Error; end
11
14
 
15
+ class InvalidArgumentError < EvaluateError
16
+ attr_reader :code
17
+
18
+ def initialize(key)
19
+ super("invalid argument #{key}")
20
+ @code = :invalid_argument
21
+ end
22
+ end
23
+
24
+ class NoSuchKeyError < EvaluateError
25
+ attr_reader :code
26
+
27
+ def initialize(var, attrib)
28
+ super("No such key: #{attrib} in #{var}")
29
+ @code = :no_such_key
30
+ end
31
+ end
32
+
12
33
  class NoSuchFieldError < EvaluateError
13
34
  attr_reader :code
14
35
 
@@ -18,7 +39,7 @@ module Cel
18
39
  end
19
40
  end
20
41
 
21
- class NoMatchingOverloadError < CheckError
42
+ class NoMatchingOverloadError < EvaluateError
22
43
  attr_reader :code
23
44
 
24
45
  def initialize(op)
data/lib/cel/macro.rb CHANGED
@@ -12,66 +12,132 @@ module Cel
12
12
  # If f is a oneof or singular message field, has(e.f) indicates whether the field is set.
13
13
  # If f is some other singular field, has(e.f) indicates whether the field's value is its default
14
14
  # value (zero for numeric fields, false for booleans, empty for strings and bytes).
15
- def has(invoke)
16
- var = invoke.var
15
+ def has(invoke, program:)
17
16
  func = invoke.func
17
+ var = program.disable_cel_conversion do
18
+ program.evaluate(invoke.var)
19
+ end
18
20
 
19
21
  case var
20
- when Message
22
+ when Protobuf.base_class
21
23
  # If e evaluates to a message and f is not a declared field for the message,
22
24
  # has(e.f) raises a no_such_field error.
23
- raise NoSuchFieldError.new(var, func) unless var.field?(func)
25
+ raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)
26
+
27
+ value = var.public_send(func)
28
+ field = var.class.descriptor.lookup(func.to_s)
24
29
 
25
- Bool.new(!var.public_send(func).nil?)
30
+ if field.label == :repeated
31
+ # If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.
32
+ Bool.cast(field.get(var).size.positive?)
33
+ elsif field.has_presence?
34
+ # If f is a oneof or singular message field, has(e.f) indicates whether the field is set.
35
+ Bool.cast(field.has?(var))
36
+ else
37
+ # If f is some other singular field, has(e.f) indicates whether the field's value is its
38
+ # default value (zero for numeric fields, false for booleans, empty for strings and bytes).
39
+ value = field.get(var)
40
+ case field.type
41
+ when :bool
42
+ Bool.cast(value == true)
43
+ when :string, :bytes
44
+ Bool.cast(!value.empty?)
45
+ when :enum
46
+ Bool.cast(value != field.default)
47
+ else
48
+ Bool.cast(value != 0)
49
+ end
50
+ end
26
51
  when Map
27
52
  # If e evaluates to a map, then has(e.f) indicates whether the string f
28
53
  # is a key in the map (note that f must syntactically be an identifier).
29
- Bool.new(var.respond_to?(func))
54
+ Bool.cast(var.respond_to?(func))
30
55
  else
31
56
  # In all other cases, has(e.f) evaluates to an error.
32
57
  raise EvaluateError, "#{invoke} is not supported"
33
58
  end
34
59
  end
35
60
 
36
- def size(literal)
37
- literal.size
61
+ def size(literal, program: nil)
62
+ literal = program.evaluate(literal) if program
63
+ Cel::Number.new(:int, program.evaluate(literal).size)
38
64
  end
39
65
 
40
- def matches(string, pattern)
66
+ def matches(string, pattern, program: nil)
67
+ pattern = program.evaluate(pattern) if program
41
68
  pattern = Regexp.new(pattern)
42
- Bool.new(pattern.match?(string))
69
+ Bool.cast(pattern.match?(string))
70
+ end
71
+
72
+ def all(collection, *identifiers, predicate, program:)
73
+ identifiers = identifiers.map(&:to_sym)
74
+ error = nil
75
+
76
+ return_value = with_context(collection, identifiers).map do |context|
77
+ program.with_extra_context(context).evaluate(predicate).value
78
+ rescue StandardError => e
79
+ error = e
80
+ end
81
+
82
+ has_false = return_value.include?(false)
83
+
84
+ # if any predicate evaluates to false, the macro evaluates to false, ignoring any errors
85
+ # from other predicates.
86
+ raise error if error && !has_false
87
+
88
+ Bool.cast(!has_false)
43
89
  end
44
90
 
45
- def all(collection, identifier, predicate, context:)
46
- return_value = collection.all? do |element, *|
47
- Program.new(context.merge(identifier.to_sym => element)).evaluate(predicate).value
91
+ def exists(collection, *identifiers, predicate, program:)
92
+ identifiers = identifiers.map(&:to_sym)
93
+
94
+ return_value = with_context(collection, identifiers).any? do |context|
95
+ program.with_extra_context(context).evaluate(predicate).value
48
96
  end
49
- Bool.new(return_value)
97
+ Bool.cast(return_value)
50
98
  end
51
99
 
52
- def exists(collection, identifier, predicate, context:)
53
- return_value = collection.any? do |element, *|
54
- Program.new(context.merge(identifier.to_sym => element)).evaluate(predicate).value
100
+ def exists_one(collection, *identifiers, predicate, program:)
101
+ identifiers = identifiers.map(&:to_sym)
102
+
103
+ # This macro does not short-circuit in order to remain consistent with logical operators
104
+ # being the only operators which can absorb errors within CEL.
105
+ return_value = with_context(collection, identifiers).select do |context|
106
+ program.with_extra_context(context).evaluate(predicate).value
55
107
  end
56
- Bool.new(return_value)
108
+ Bool.cast(return_value.size == 1)
57
109
  end
58
110
 
59
- def exists_one(collection, identifier, predicate, context:)
60
- return_value = collection.one? do |element, *|
61
- Program.new(context.merge(identifier.to_sym => element)).evaluate(predicate).value
111
+ def filter(collection, *identifiers, predicate, program:)
112
+ identifiers = identifiers.map(&:to_sym)
113
+
114
+ return_value = with_context(collection, identifiers).filter_map do |context|
115
+ next unless program.with_extra_context(context).evaluate(predicate).value
116
+
117
+ context.values.last
62
118
  end
63
- Bool.new(return_value)
119
+ List.new(return_value)
64
120
  end
65
121
 
66
- def filter(collection, identifier, predicate, context:)
67
- collection.select do |element, *|
68
- Program.new(context.merge(identifier.to_sym => element)).evaluate(predicate).value
122
+ def map(collection, *identifiers, predicate, program:)
123
+ identifiers = identifiers.map(&:to_sym)
124
+
125
+ return_value = with_context(collection, identifiers).map do |context|
126
+ program.with_extra_context(context).evaluate(predicate)
69
127
  end
128
+ List.new(return_value)
70
129
  end
71
130
 
72
- def map(collection, identifier, predicate, context:)
73
- collection.map do |element, *|
74
- Program.new(context.merge(identifier.to_sym => element)).evaluate(predicate)
131
+ def with_context(collection, identifiers)
132
+ case collection
133
+ when Map
134
+ raise EvaluateError, "can only support 2 identifiers" unless identifiers.size <= 2
135
+ else
136
+ collection = collection.each_cons(identifiers.size)
137
+ end
138
+
139
+ collection.map do |elements|
140
+ identifiers.zip(elements).to_h
75
141
  end
76
142
  end
77
143
  end