Module: Ruby::Rego::Builtins::Aggregates

Defined in:
lib/ruby/rego/builtins/aggregates.rb

Overview

Built-in aggregation helpers.

Constant Summary collapse

AGGREGATE_FUNCTIONS =
{
  "count" => :count,
  "sum" => :sum,
  "product" => :product,
  "max" => :max,
  "min" => :min,
  "all" => :all,
  "any" => :any
}.freeze

Class Method Summary collapse

Class Method Details

.all(array) ⇒ Ruby::Rego::BooleanValue

Parameters:

Returns:



133
134
135
136
# File 'lib/ruby/rego/builtins/aggregates.rb', line 133

def self.all(array)
  Base.assert_type(array, expected: ArrayValue, context: "all")
  BooleanValue.new(array.value.all?(&:truthy?))
end

.any(array) ⇒ Ruby::Rego::BooleanValue

Parameters:

Returns:



140
141
142
143
# File 'lib/ruby/rego/builtins/aggregates.rb', line 140

def self.any(array)
  Base.assert_type(array, expected: ArrayValue, context: "any")
  BooleanValue.new(array.value.any?(&:truthy?))
end

.count(collection) ⇒ Ruby::Rego::NumberValue

Parameters:

Returns:



42
43
44
45
46
47
48
49
50
# File 'lib/ruby/rego/builtins/aggregates.rb', line 42

def self.count(collection)
  Base.assert_type(
    collection,
    expected: [ArrayValue, ObjectValue, SetValue, StringValue],
    context: "count"
  )

  NumberValue.new(collection.value.size)
end

.max(collection) ⇒ Ruby::Rego::Value

Return the largest element of an array or set. DIVERGENCE (pre-existing, tracked): OPA’s max/min are polymorphic over its total value order — max(["a","b"]) -> "b", max([true,false]) -> true, even mixed types max([1,"a"]) -> "a" — whereas this gem restricts max/min to numbers (a non-number element maps to undefined via extract_finite_real). Generalizing to OPA’s value order is its own number-model/ordering sweep item, not this (numeric-aggregates) scope.

Parameters:

Returns:



105
106
107
108
109
110
111
112
113
114
# File 'lib/ruby/rego/builtins/aggregates.rb', line 105

def self.max(collection)
  numbers = extract_numeric_elements(collection, name: "max")
  ensure_non_empty(numbers, name: "max")
  # Among value-equal extrema OPA returns the LAST element (so max([1.50, 1.5]) -> 1.5, keeping
  # the later spelling). A single-pass reduce keeping the later element on a tie (the explicit
  # `>=` documents the tie-break) avoids the reversed-array copy that `reverse.max` would allocate.
  # rubocop:disable Style/MinMaxComparison
  Value.from_ruby(numbers.reduce { |best, number| number >= best ? number : best })
  # rubocop:enable Style/MinMaxComparison
end

.min(collection) ⇒ Ruby::Rego::Value

Return the smallest element of an array or set. Number-only, like max — see it for the pre-existing divergence from OPA’s polymorphic min.

Parameters:

Returns:



121
122
123
124
125
126
127
128
129
# File 'lib/ruby/rego/builtins/aggregates.rb', line 121

def self.min(collection)
  numbers = extract_numeric_elements(collection, name: "min")
  ensure_non_empty(numbers, name: "min")
  # OPA returns the LAST element among value-equal minima too; reduce keeping the later element
  # on a tie (single pass, no reversed-array copy).
  # rubocop:disable Style/MinMaxComparison
  Value.from_ruby(numbers.reduce { |best, number| number <= best ? number : best })
  # rubocop:enable Style/MinMaxComparison
end

.product(collection) ⇒ Ruby::Rego::Value

Multiply an array or set of numbers. The OPA-faithful arithmetic (prec-64 big.Float fold with no integer fast-path) and the DoS magnitude cap live in Number.product; here an empty collection is the multiplicative identity 1 and a set is folded in ascending order by extract_numeric_elements.

Parameters:

Returns:



69
70
71
72
# File 'lib/ruby/rego/builtins/aggregates.rb', line 69

def self.product(collection)
  numbers = extract_numeric_elements(collection, name: "product")
  Value.from_ruby(bounded_fold(:product, numbers))
end

.register!Ruby::Rego::Builtins::BuiltinRegistry



23
24
25
26
27
28
29
30
31
# File 'lib/ruby/rego/builtins/aggregates.rb', line 23

def self.register!
  registry = BuiltinRegistry.instance

  AGGREGATE_FUNCTIONS.each do |name, handler|
    register_function(registry, name, handler)
  end

  registry
end

.sum(collection) ⇒ Ruby::Rego::Value

Sum an array or set of numbers. The OPA-faithful arithmetic (integer fast-path vs prec-64 big.Float fold, and the int64-overflow handling) lives in Number.sum; here an empty collection sums to 0 and a set is deduplicated and folded in ascending order by extract_numeric_elements.

Parameters:

Returns:



58
59
60
61
# File 'lib/ruby/rego/builtins/aggregates.rb', line 58

def self.sum(collection)
  numbers = extract_numeric_elements(collection, name: "sum")
  Value.from_ruby(bounded_fold(:sum, numbers))
end