Module: Cel::Macro

Defined in:
lib/cel/macro.rb

Class Method Summary collapse

Class Method Details

.all(collection, *identifiers, predicate, program:) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/cel/macro.rb', line 72

def all(collection, *identifiers, predicate, program:)
  identifiers = identifiers.map(&:to_sym)
  error = nil

  return_value = with_context(collection, identifiers).map do |context|
    program.with_extra_context(context).evaluate(predicate).value
  rescue StandardError => e
    error = e
  end

  has_false = return_value.include?(false)

  # if any predicate evaluates to false, the macro evaluates to false, ignoring any errors
  # from other predicates.
  raise error if error && !has_false

  Bool.cast(!has_false)
end

.exists(collection, *identifiers, predicate, program:) ⇒ Object



91
92
93
94
95
96
97
98
# File 'lib/cel/macro.rb', line 91

def exists(collection, *identifiers, predicate, program:)
  identifiers = identifiers.map(&:to_sym)

  return_value = with_context(collection, identifiers).any? do |context|
    program.with_extra_context(context).evaluate(predicate).value
  end
  Bool.cast(return_value)
end

.exists_one(collection, *identifiers, predicate, program:) ⇒ Object



100
101
102
103
104
105
106
107
108
109
# File 'lib/cel/macro.rb', line 100

def exists_one(collection, *identifiers, predicate, program:)
  identifiers = identifiers.map(&:to_sym)

  # This macro does not short-circuit in order to remain consistent with logical operators
  # being the only operators which can absorb errors within CEL.
  return_value = with_context(collection, identifiers).select do |context|
    program.with_extra_context(context).evaluate(predicate).value
  end
  Bool.cast(return_value.size == 1)
end

.filter(collection, *identifiers, predicate, program:) ⇒ Object



111
112
113
114
115
116
117
118
119
120
# File 'lib/cel/macro.rb', line 111

def filter(collection, *identifiers, predicate, program:)
  identifiers = identifiers.map(&:to_sym)

  return_value = with_context(collection, identifiers).filter_map do |context|
    next unless program.with_extra_context(context).evaluate(predicate).value

    context.values.last
  end
  List.new(return_value)
end

.has(invoke, program:) ⇒ Object

If e evaluates to a protocol buffers version 2 message and f is a defined field:

If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.
If f is a singular or oneof field, has(e.f) indicates whether the field is set.

If e evaluates to a protocol buffers version 3 message and f is a defined field:

If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.
If f is a oneof or singular message field, has(e.f) indicates whether the field is set.
If f is some other singular field, has(e.f) indicates whether the field's value is its default
  value (zero for numeric fields, false for booleans, empty for strings and bytes).


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/cel/macro.rb', line 15

def has(invoke, program:)
  func = invoke.func
  var = program.disable_cel_conversion do
    program.evaluate(invoke.var)
  end

  case var
  when Protobuf.base_class
    # If e evaluates to a message and f is not a declared field for the message,
    # has(e.f) raises a no_such_field error.
    raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)

    value = var.public_send(func)
    field = var.class.descriptor.lookup(func.to_s)

    if field.label == :repeated
      # If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.
      Bool.cast(field.get(var).size.positive?)
    elsif field.has_presence?
      # If f is a oneof or singular message field, has(e.f) indicates whether the field is set.
      Bool.cast(field.has?(var))
    else
      # If f is some other singular field, has(e.f) indicates whether the field's value is its
      # default value (zero for numeric fields, false for booleans, empty for strings and bytes).
      value = field.get(var)
      case field.type
      when :bool
        Bool.cast(value == true)
      when :string, :bytes
        Bool.cast(!value.empty?)
      when :enum
        Bool.cast(value != field.default)
      else
        Bool.cast(value != 0)
      end
    end
  when Map
    # If e evaluates to a map, then has(e.f) indicates whether the string f
    # is a key in the map (note that f must syntactically be an identifier).
    Bool.cast(var.respond_to?(func))
  else
    # In all other cases, has(e.f) evaluates to an error.
    raise EvaluateError, "#{invoke} is not supported"
  end
end

.map(collection, *identifiers, predicate, program:) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/cel/macro.rb', line 122

def map(collection, *identifiers, predicate, program:)
  identifiers = identifiers.map(&:to_sym)

  return_value = with_context(collection, identifiers).map do |context|
    program.with_extra_context(context).evaluate(predicate)
  end
  List.new(return_value)
end

.matches(string, pattern, program: nil) ⇒ Object



66
67
68
69
70
# File 'lib/cel/macro.rb', line 66

def matches(string, pattern, program: nil)
  pattern = program.evaluate(pattern) if program
  pattern = Regexp.new(pattern)
  Bool.cast(pattern.match?(string))
end

.size(literal, program: nil) ⇒ Object



61
62
63
64
# File 'lib/cel/macro.rb', line 61

def size(literal, program: nil)
  literal = program.evaluate(literal) if program
  Cel::Number.new(:int, program.evaluate(literal).size)
end

.with_context(collection, identifiers) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cel/macro.rb', line 131

def with_context(collection, identifiers)
  case collection
  when Map
    raise EvaluateError, "can only support 2 identifiers" unless identifiers.size <= 2
  else
    collection = collection.each_cons(identifiers.size)
  end

  collection.map do |elements|
    identifiers.zip(elements).to_h
  end
end