ruby-rego 0.1.0
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 +7 -0
- data/.reek.yml +80 -0
- data/.vscode/extensions.json +19 -0
- data/.vscode/launch.json +35 -0
- data/.vscode/settings.json +25 -0
- data/.vscode/tasks.json +117 -0
- data/.yardopts +12 -0
- data/ARCHITECTURE.md +39 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +183 -0
- data/RELEASING.md +37 -0
- data/Rakefile +38 -0
- data/SECURITY.md +26 -0
- data/Steepfile +10 -0
- data/TODO.md +35 -0
- data/benchmark/builtin_calls.rb +29 -0
- data/benchmark/complex_policy.rb +19 -0
- data/benchmark/comprehensions.rb +19 -0
- data/benchmark/simple_rules.rb +20 -0
- data/examples/README.md +27 -0
- data/examples/sample_config.yaml +2 -0
- data/examples/simple_policy.rego +7 -0
- data/examples/validation_policy.rego +11 -0
- data/exe/rego-validate +6 -0
- data/lib/ruby/rego/ast/base.rb +95 -0
- data/lib/ruby/rego/ast/binary_op.rb +64 -0
- data/lib/ruby/rego/ast/call.rb +27 -0
- data/lib/ruby/rego/ast/composite.rb +48 -0
- data/lib/ruby/rego/ast/comprehension.rb +63 -0
- data/lib/ruby/rego/ast/every.rb +37 -0
- data/lib/ruby/rego/ast/import.rb +32 -0
- data/lib/ruby/rego/ast/literal.rb +70 -0
- data/lib/ruby/rego/ast/module.rb +32 -0
- data/lib/ruby/rego/ast/package.rb +22 -0
- data/lib/ruby/rego/ast/query.rb +63 -0
- data/lib/ruby/rego/ast/reference.rb +58 -0
- data/lib/ruby/rego/ast/rule.rb +114 -0
- data/lib/ruby/rego/ast/unary_op.rb +42 -0
- data/lib/ruby/rego/ast/variable.rb +22 -0
- data/lib/ruby/rego/ast.rb +17 -0
- data/lib/ruby/rego/builtins/aggregates.rb +124 -0
- data/lib/ruby/rego/builtins/base.rb +95 -0
- data/lib/ruby/rego/builtins/collections/array_ops.rb +103 -0
- data/lib/ruby/rego/builtins/collections/object_ops.rb +120 -0
- data/lib/ruby/rego/builtins/collections/set_ops.rb +51 -0
- data/lib/ruby/rego/builtins/collections.rb +137 -0
- data/lib/ruby/rego/builtins/comparisons/casts.rb +139 -0
- data/lib/ruby/rego/builtins/comparisons.rb +84 -0
- data/lib/ruby/rego/builtins/numeric_helpers.rb +56 -0
- data/lib/ruby/rego/builtins/registry.rb +199 -0
- data/lib/ruby/rego/builtins/registry_helpers.rb +27 -0
- data/lib/ruby/rego/builtins/strings/case_ops.rb +22 -0
- data/lib/ruby/rego/builtins/strings/concat.rb +19 -0
- data/lib/ruby/rego/builtins/strings/formatting.rb +35 -0
- data/lib/ruby/rego/builtins/strings/helpers.rb +62 -0
- data/lib/ruby/rego/builtins/strings/number_helpers.rb +48 -0
- data/lib/ruby/rego/builtins/strings/search.rb +63 -0
- data/lib/ruby/rego/builtins/strings/split.rb +19 -0
- data/lib/ruby/rego/builtins/strings/substring.rb +22 -0
- data/lib/ruby/rego/builtins/strings/trim.rb +42 -0
- data/lib/ruby/rego/builtins/strings/trim_helpers.rb +62 -0
- data/lib/ruby/rego/builtins/strings.rb +58 -0
- data/lib/ruby/rego/builtins/types.rb +89 -0
- data/lib/ruby/rego/call_name.rb +55 -0
- data/lib/ruby/rego/cli.rb +1122 -0
- data/lib/ruby/rego/compiled_module.rb +114 -0
- data/lib/ruby/rego/compiler.rb +1097 -0
- data/lib/ruby/rego/environment/overrides.rb +33 -0
- data/lib/ruby/rego/environment/reference_resolution.rb +86 -0
- data/lib/ruby/rego/environment.rb +230 -0
- data/lib/ruby/rego/environment_pool.rb +71 -0
- data/lib/ruby/rego/error_handling.rb +58 -0
- data/lib/ruby/rego/error_payload.rb +34 -0
- data/lib/ruby/rego/errors.rb +196 -0
- data/lib/ruby/rego/evaluator/assignment_support.rb +126 -0
- data/lib/ruby/rego/evaluator/binding_helpers.rb +60 -0
- data/lib/ruby/rego/evaluator/comprehension_evaluator.rb +182 -0
- data/lib/ruby/rego/evaluator/expression_dispatch.rb +45 -0
- data/lib/ruby/rego/evaluator/expression_evaluator.rb +492 -0
- data/lib/ruby/rego/evaluator/object_literal_evaluator.rb +52 -0
- data/lib/ruby/rego/evaluator/operator_evaluator.rb +163 -0
- data/lib/ruby/rego/evaluator/query_node_builder.rb +38 -0
- data/lib/ruby/rego/evaluator/reference_key_resolver.rb +50 -0
- data/lib/ruby/rego/evaluator/reference_resolver.rb +352 -0
- data/lib/ruby/rego/evaluator/rule_evaluator/bindings.rb +70 -0
- data/lib/ruby/rego/evaluator/rule_evaluator.rb +550 -0
- data/lib/ruby/rego/evaluator/rule_value_provider.rb +56 -0
- data/lib/ruby/rego/evaluator/variable_collector.rb +221 -0
- data/lib/ruby/rego/evaluator.rb +174 -0
- data/lib/ruby/rego/lexer/number_reader.rb +68 -0
- data/lib/ruby/rego/lexer/stream.rb +137 -0
- data/lib/ruby/rego/lexer/string_reader.rb +90 -0
- data/lib/ruby/rego/lexer/template_string_reader.rb +62 -0
- data/lib/ruby/rego/lexer.rb +206 -0
- data/lib/ruby/rego/location.rb +73 -0
- data/lib/ruby/rego/memoization.rb +67 -0
- data/lib/ruby/rego/parser/collections.rb +173 -0
- data/lib/ruby/rego/parser/expressions.rb +216 -0
- data/lib/ruby/rego/parser/precedence.rb +42 -0
- data/lib/ruby/rego/parser/query.rb +139 -0
- data/lib/ruby/rego/parser/references.rb +115 -0
- data/lib/ruby/rego/parser/rules.rb +310 -0
- data/lib/ruby/rego/parser.rb +210 -0
- data/lib/ruby/rego/policy.rb +50 -0
- data/lib/ruby/rego/result.rb +91 -0
- data/lib/ruby/rego/token.rb +206 -0
- data/lib/ruby/rego/unifier.rb +451 -0
- data/lib/ruby/rego/value.rb +379 -0
- data/lib/ruby/rego/version.rb +7 -0
- data/lib/ruby/rego/with_modifiers/with_modifier.rb +37 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_applier.rb +48 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_builtin_override.rb +128 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_context.rb +120 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_path_key_resolver.rb +42 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_path_override.rb +99 -0
- data/lib/ruby/rego/with_modifiers/with_modifier_root_scope.rb +58 -0
- data/lib/ruby/rego.rb +72 -0
- data/sig/objspace.rbs +4 -0
- data/sig/psych.rbs +7 -0
- data/sig/rego_validate.rbs +382 -0
- data/sig/ruby/rego.rbs +2150 -0
- metadata +172 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
module AST
|
|
8
|
+
# Represents a query literal with optional with modifiers.
|
|
9
|
+
class QueryLiteral < Base
|
|
10
|
+
# @param expression [Object]
|
|
11
|
+
# @param with_modifiers [Array<WithModifier>]
|
|
12
|
+
# @param location [Location, nil]
|
|
13
|
+
def initialize(expression:, with_modifiers: [], location: nil)
|
|
14
|
+
super(location: location)
|
|
15
|
+
@expression = expression
|
|
16
|
+
@with_modifiers = with_modifiers.dup.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Object]
|
|
20
|
+
attr_reader :expression
|
|
21
|
+
|
|
22
|
+
# @return [Array<WithModifier>]
|
|
23
|
+
attr_reader :with_modifiers
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Represents a `some` declaration.
|
|
27
|
+
class SomeDecl < Base
|
|
28
|
+
# @param variables [Array<Variable>]
|
|
29
|
+
# @param collection [Object, nil]
|
|
30
|
+
# @param location [Location, nil]
|
|
31
|
+
def initialize(variables:, collection: nil, location: nil)
|
|
32
|
+
super(location: location)
|
|
33
|
+
@variables = variables.dup.freeze
|
|
34
|
+
@collection = collection
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Array<Variable>]
|
|
38
|
+
attr_reader :variables
|
|
39
|
+
|
|
40
|
+
# @return [Object, nil]
|
|
41
|
+
attr_reader :collection
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Represents a `with` modifier.
|
|
45
|
+
class WithModifier < Base
|
|
46
|
+
# @param target [Object]
|
|
47
|
+
# @param value [Object]
|
|
48
|
+
# @param location [Location, nil]
|
|
49
|
+
def initialize(target:, value:, location: nil)
|
|
50
|
+
super(location: location)
|
|
51
|
+
@target = target
|
|
52
|
+
@value = value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [Object]
|
|
56
|
+
attr_reader :target
|
|
57
|
+
|
|
58
|
+
# @return [Object]
|
|
59
|
+
attr_reader :value
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
module AST
|
|
8
|
+
# Represents a reference to nested data (e.g. input.user.roles[0]).
|
|
9
|
+
class Reference < Base
|
|
10
|
+
# @param base [Object]
|
|
11
|
+
# @param path [Array<RefArg>]
|
|
12
|
+
# @param location [Location, nil]
|
|
13
|
+
def initialize(base:, path:, location: nil)
|
|
14
|
+
super(location: location)
|
|
15
|
+
@base = base
|
|
16
|
+
@path = path.dup.freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Object]
|
|
20
|
+
attr_reader :base
|
|
21
|
+
|
|
22
|
+
# @return [Array<RefArg>]
|
|
23
|
+
attr_reader :path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Base class for reference path arguments.
|
|
27
|
+
class RefArg < Base
|
|
28
|
+
# @param value [Object]
|
|
29
|
+
# @param location [Location, nil]
|
|
30
|
+
def initialize(value:, location: nil)
|
|
31
|
+
super(location: location)
|
|
32
|
+
@value = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Object]
|
|
36
|
+
attr_reader :value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Dot-based reference argument (e.g. .foo).
|
|
40
|
+
class DotRefArg < RefArg
|
|
41
|
+
# @param value [Object]
|
|
42
|
+
# @param location [Location, nil]
|
|
43
|
+
def initialize(value:, location: nil)
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Bracket-based reference argument (e.g. [0]).
|
|
49
|
+
class BracketRefArg < RefArg
|
|
50
|
+
# @param value [Object]
|
|
51
|
+
# @param location [Location, nil]
|
|
52
|
+
def initialize(value:, location: nil)
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
module AST
|
|
8
|
+
# Represents a rule definition.
|
|
9
|
+
class Rule < Base
|
|
10
|
+
RULE_TYPE_LOOKUP = %i[rule_type type kind].freeze
|
|
11
|
+
|
|
12
|
+
# Bundles rule components for storage.
|
|
13
|
+
Definition = Struct.new(:head, :body, :default_value, :else_clause, keyword_init: true)
|
|
14
|
+
|
|
15
|
+
# @param name [String]
|
|
16
|
+
# @param head [Object, nil]
|
|
17
|
+
# @param body [Object, nil]
|
|
18
|
+
# @param default_value [Object, nil]
|
|
19
|
+
# @param else_clause [Object, nil]
|
|
20
|
+
# @param location [Location, nil]
|
|
21
|
+
# :reek:LongParameterList
|
|
22
|
+
def initialize(name:, head: nil, body: nil, default_value: nil, else_clause: nil, location: nil) # rubocop:disable Metrics/ParameterLists
|
|
23
|
+
super(location: location)
|
|
24
|
+
@name = name
|
|
25
|
+
@definition = Definition.new(
|
|
26
|
+
head: head,
|
|
27
|
+
body: body,
|
|
28
|
+
default_value: default_value,
|
|
29
|
+
else_clause: else_clause
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String]
|
|
34
|
+
attr_reader :name
|
|
35
|
+
|
|
36
|
+
# @return [Object, nil]
|
|
37
|
+
def head
|
|
38
|
+
definition.head
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Object, nil]
|
|
42
|
+
def body
|
|
43
|
+
definition.body
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Object, nil]
|
|
47
|
+
def default_value
|
|
48
|
+
definition.default_value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Object, nil]
|
|
52
|
+
def else_clause
|
|
53
|
+
definition.else_clause
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def complete?
|
|
58
|
+
rule_type == :complete
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def partial_set?
|
|
63
|
+
rule_type == :partial_set
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def partial_object?
|
|
68
|
+
rule_type == :partial_object
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def function?
|
|
73
|
+
rule_type == :function
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# @return [Symbol, nil]
|
|
79
|
+
def rule_type
|
|
80
|
+
return nil unless head
|
|
81
|
+
|
|
82
|
+
type = resolve_rule_type
|
|
83
|
+
|
|
84
|
+
type.is_a?(String) ? type.to_sym : type
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def resolve_rule_type
|
|
88
|
+
type_from_object || type_from_hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# :reek:ManualDispatch
|
|
92
|
+
def type_from_object
|
|
93
|
+
RULE_TYPE_LOOKUP.each do |method|
|
|
94
|
+
return head.public_send(method) if head.respond_to?(method)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def type_from_hash
|
|
101
|
+
return nil unless head.is_a?(Hash)
|
|
102
|
+
|
|
103
|
+
RULE_TYPE_LOOKUP.each do |key|
|
|
104
|
+
return head[key] if head.key?(key)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
attr_reader :definition
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
module AST
|
|
8
|
+
# Represents a unary operation (e.g. negation).
|
|
9
|
+
class UnaryOp < Base
|
|
10
|
+
OPERATORS = %i[not minus].freeze
|
|
11
|
+
|
|
12
|
+
# @param operator [Symbol]
|
|
13
|
+
# @param operand [Object]
|
|
14
|
+
# @param location [Location, nil]
|
|
15
|
+
def initialize(operator:, operand:, location: nil)
|
|
16
|
+
@operator = operator
|
|
17
|
+
validate_operator!
|
|
18
|
+
super(location: location)
|
|
19
|
+
@operand = operand
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Symbol]
|
|
23
|
+
attr_reader :operator
|
|
24
|
+
|
|
25
|
+
# @return [Object]
|
|
26
|
+
attr_reader :operand
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def validate_operator # rubocop:disable Naming/PredicateMethod
|
|
31
|
+
OPERATORS.include?(@operator)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_operator!
|
|
35
|
+
return if validate_operator
|
|
36
|
+
|
|
37
|
+
raise ArgumentError, "Unknown unary operator: #{@operator.inspect}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Ruby
|
|
6
|
+
module Rego
|
|
7
|
+
module AST
|
|
8
|
+
# Represents a variable identifier.
|
|
9
|
+
class Variable < Base
|
|
10
|
+
# @param name [String]
|
|
11
|
+
# @param location [Location, nil]
|
|
12
|
+
def initialize(name:, location: nil)
|
|
13
|
+
super(location: location)
|
|
14
|
+
@name = name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [String]
|
|
18
|
+
attr_reader :name
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ast/base"
|
|
4
|
+
require_relative "ast/literal"
|
|
5
|
+
require_relative "ast/variable"
|
|
6
|
+
require_relative "ast/reference"
|
|
7
|
+
require_relative "ast/binary_op"
|
|
8
|
+
require_relative "ast/unary_op"
|
|
9
|
+
require_relative "ast/composite"
|
|
10
|
+
require_relative "ast/comprehension"
|
|
11
|
+
require_relative "ast/every"
|
|
12
|
+
require_relative "ast/query"
|
|
13
|
+
require_relative "ast/call"
|
|
14
|
+
require_relative "ast/module"
|
|
15
|
+
require_relative "ast/package"
|
|
16
|
+
require_relative "ast/import"
|
|
17
|
+
require_relative "ast/rule"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "registry"
|
|
5
|
+
|
|
6
|
+
module Ruby
|
|
7
|
+
module Rego
|
|
8
|
+
module Builtins
|
|
9
|
+
# Built-in aggregation helpers.
|
|
10
|
+
module Aggregates
|
|
11
|
+
AGGREGATE_FUNCTIONS = {
|
|
12
|
+
"count" => :count,
|
|
13
|
+
"sum" => :sum,
|
|
14
|
+
"max" => :max,
|
|
15
|
+
"min" => :min,
|
|
16
|
+
"all" => :all,
|
|
17
|
+
"any" => :any
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# @return [Ruby::Rego::Builtins::BuiltinRegistry]
|
|
21
|
+
def self.register!
|
|
22
|
+
registry = BuiltinRegistry.instance
|
|
23
|
+
|
|
24
|
+
AGGREGATE_FUNCTIONS.each do |name, handler|
|
|
25
|
+
register_function(registry, name, handler)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
registry
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.register_function(registry, name, handler)
|
|
32
|
+
return if registry.registered?(name)
|
|
33
|
+
|
|
34
|
+
registry.register(name, 1) { |value| public_send(handler, value) }
|
|
35
|
+
end
|
|
36
|
+
private_class_method :register_function
|
|
37
|
+
|
|
38
|
+
# @param collection [Ruby::Rego::Value]
|
|
39
|
+
# @return [Ruby::Rego::NumberValue]
|
|
40
|
+
def self.count(collection)
|
|
41
|
+
Base.assert_type(
|
|
42
|
+
collection,
|
|
43
|
+
expected: [ArrayValue, ObjectValue, SetValue, StringValue],
|
|
44
|
+
context: "count"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
NumberValue.new(collection.value.size)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param array [Ruby::Rego::Value]
|
|
51
|
+
# @return [Ruby::Rego::NumberValue]
|
|
52
|
+
def self.sum(array)
|
|
53
|
+
numbers = numeric_array(array, name: "sum")
|
|
54
|
+
NumberValue.new(numbers.sum)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param array [Ruby::Rego::Value]
|
|
58
|
+
# @return [Ruby::Rego::NumberValue]
|
|
59
|
+
def self.max(array)
|
|
60
|
+
numbers = numeric_array(array, name: "max")
|
|
61
|
+
ensure_non_empty(numbers, name: "max")
|
|
62
|
+
NumberValue.new(numbers.max)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param array [Ruby::Rego::Value]
|
|
66
|
+
# @return [Ruby::Rego::NumberValue]
|
|
67
|
+
def self.min(array)
|
|
68
|
+
numbers = numeric_array(array, name: "min")
|
|
69
|
+
ensure_non_empty(numbers, name: "min")
|
|
70
|
+
NumberValue.new(numbers.min)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param array [Ruby::Rego::Value]
|
|
74
|
+
# @return [Ruby::Rego::BooleanValue]
|
|
75
|
+
def self.all(array)
|
|
76
|
+
Base.assert_type(array, expected: ArrayValue, context: "all")
|
|
77
|
+
BooleanValue.new(array.value.all?(&:truthy?))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param array [Ruby::Rego::Value]
|
|
81
|
+
# @return [Ruby::Rego::BooleanValue]
|
|
82
|
+
def self.any(array)
|
|
83
|
+
Base.assert_type(array, expected: ArrayValue, context: "any")
|
|
84
|
+
BooleanValue.new(array.value.any?(&:truthy?))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param array [Ruby::Rego::Value]
|
|
88
|
+
# @param name [String]
|
|
89
|
+
# @return [Array<Numeric>]
|
|
90
|
+
def self.numeric_array(array, name:)
|
|
91
|
+
Base.assert_type(array, expected: ArrayValue, context: name)
|
|
92
|
+
|
|
93
|
+
array.value.map.with_index do |element, index|
|
|
94
|
+
Base.assert_type(
|
|
95
|
+
element,
|
|
96
|
+
expected: NumberValue,
|
|
97
|
+
context: "#{name} element #{index}"
|
|
98
|
+
)
|
|
99
|
+
element.value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
private_class_method :numeric_array
|
|
103
|
+
|
|
104
|
+
# @param numbers [Array<Numeric>]
|
|
105
|
+
# @param name [String]
|
|
106
|
+
# @return [void]
|
|
107
|
+
def self.ensure_non_empty(numbers, name:)
|
|
108
|
+
return unless numbers.empty?
|
|
109
|
+
|
|
110
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
111
|
+
"Expected a non-empty array",
|
|
112
|
+
expected: "non-empty array",
|
|
113
|
+
actual: numbers.size,
|
|
114
|
+
context: name,
|
|
115
|
+
location: nil
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
private_class_method :ensure_non_empty
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
Ruby::Rego::Builtins::Aggregates.register!
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
require_relative "../value"
|
|
5
|
+
|
|
6
|
+
module Ruby
|
|
7
|
+
module Rego
|
|
8
|
+
module Builtins
|
|
9
|
+
# Shared helpers for built-in function implementations.
|
|
10
|
+
module Base
|
|
11
|
+
# @param args [Array<Object>]
|
|
12
|
+
# @param expected [Integer, Array<Integer>]
|
|
13
|
+
# @param name [String, nil]
|
|
14
|
+
# @return [void]
|
|
15
|
+
def self.assert_arity(args, expected, name: nil)
|
|
16
|
+
actual = args.size
|
|
17
|
+
return if arity_valid?(actual, expected)
|
|
18
|
+
|
|
19
|
+
raise_builtin_arity_error(actual, expected, name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param value [Object]
|
|
23
|
+
# @param expected [Class, Array<Class>]
|
|
24
|
+
# @param context [String, nil]
|
|
25
|
+
# @return [void]
|
|
26
|
+
def self.assert_type(value, expected:, context: nil)
|
|
27
|
+
expected_classes = normalize_expected(expected)
|
|
28
|
+
return if expected_classes.any? { |klass| value.is_a?(klass) }
|
|
29
|
+
|
|
30
|
+
raise_type_error(
|
|
31
|
+
expected: expected_classes.map(&:name).join(" or "),
|
|
32
|
+
actual: value.class.name,
|
|
33
|
+
context: context
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param value [Object]
|
|
38
|
+
# @return [Object]
|
|
39
|
+
def self.to_ruby(value)
|
|
40
|
+
value.is_a?(Ruby::Rego::Value) ? value.to_ruby : value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param value [Object]
|
|
44
|
+
# @return [Ruby::Rego::Value]
|
|
45
|
+
def self.to_value(value)
|
|
46
|
+
Ruby::Rego::Value.from_ruby(value)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.normalize_expected(expected)
|
|
50
|
+
expected.is_a?(Array) ? expected : [expected]
|
|
51
|
+
end
|
|
52
|
+
private_class_method :normalize_expected
|
|
53
|
+
|
|
54
|
+
def self.normalize_arity(expected)
|
|
55
|
+
expected.is_a?(Array) ? expected : [expected]
|
|
56
|
+
end
|
|
57
|
+
private_class_method :normalize_arity
|
|
58
|
+
|
|
59
|
+
def self.format_arity(expected_list)
|
|
60
|
+
expected_list.map(&:to_i).uniq.sort.join(" or ")
|
|
61
|
+
end
|
|
62
|
+
private_class_method :format_arity
|
|
63
|
+
|
|
64
|
+
def self.arity_valid?(actual, expected)
|
|
65
|
+
normalize_arity(expected).include?(actual)
|
|
66
|
+
end
|
|
67
|
+
private_class_method :arity_valid?
|
|
68
|
+
|
|
69
|
+
def self.raise_builtin_arity_error(actual, expected, name)
|
|
70
|
+
context = name ? "builtin #{name}" : nil
|
|
71
|
+
expected_list = normalize_arity(expected)
|
|
72
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
73
|
+
"Wrong number of arguments",
|
|
74
|
+
expected: format_arity(expected_list),
|
|
75
|
+
actual: actual,
|
|
76
|
+
context: context,
|
|
77
|
+
location: nil
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
private_class_method :raise_builtin_arity_error
|
|
81
|
+
|
|
82
|
+
def self.raise_type_error(expected:, actual:, context: nil)
|
|
83
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
84
|
+
"Type mismatch",
|
|
85
|
+
expected: expected,
|
|
86
|
+
actual: actual,
|
|
87
|
+
context: context,
|
|
88
|
+
location: nil
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
private_class_method :raise_type_error
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../numeric_helpers"
|
|
5
|
+
require_relative "../../errors"
|
|
6
|
+
require_relative "../../value"
|
|
7
|
+
|
|
8
|
+
module Ruby
|
|
9
|
+
module Rego
|
|
10
|
+
module Builtins
|
|
11
|
+
module Collections
|
|
12
|
+
# Array-focused collection helpers.
|
|
13
|
+
module ArrayOps
|
|
14
|
+
# @param array [Ruby::Rego::Value]
|
|
15
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
16
|
+
def self.sort(array)
|
|
17
|
+
elements = array_values(array, name: "sort")
|
|
18
|
+
return ArrayValue.new([]) if elements.empty?
|
|
19
|
+
|
|
20
|
+
ensure_uniform_sort_type(elements)
|
|
21
|
+
sorted = elements.sort_by(&:to_ruby)
|
|
22
|
+
ArrayValue.new(sorted)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param left [Ruby::Rego::Value]
|
|
26
|
+
# @param right [Ruby::Rego::Value]
|
|
27
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
28
|
+
def self.array_concat(left, right)
|
|
29
|
+
left_values = array_values(left, name: "array.concat left")
|
|
30
|
+
right_values = array_values(right, name: "array.concat right")
|
|
31
|
+
ArrayValue.new(left_values + right_values)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param array [Ruby::Rego::Value]
|
|
35
|
+
# @param start [Ruby::Rego::Value]
|
|
36
|
+
# @param stop [Ruby::Rego::Value]
|
|
37
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
38
|
+
def self.array_slice(array, start, stop)
|
|
39
|
+
elements = array_values(array, name: "array.slice array")
|
|
40
|
+
start_index, stop_index = slice_indices(start, stop)
|
|
41
|
+
ArrayValue.new(slice_elements(elements, start_index, stop_index))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.array_values(value, name:)
|
|
45
|
+
Base.assert_type(value, expected: ArrayValue, context: name)
|
|
46
|
+
value.value
|
|
47
|
+
end
|
|
48
|
+
private_class_method :array_values
|
|
49
|
+
|
|
50
|
+
def self.ensure_uniform_sort_type(elements)
|
|
51
|
+
type = sort_type(elements)
|
|
52
|
+
elements.drop(1).each_with_index do |element, index|
|
|
53
|
+
validate_sort_element(element, type, index + 1)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
private_class_method :ensure_uniform_sort_type
|
|
57
|
+
|
|
58
|
+
def self.sort_type(elements)
|
|
59
|
+
first = elements.first
|
|
60
|
+
Base.assert_type(first, expected: [NumberValue, StringValue], context: "sort element 0")
|
|
61
|
+
first.class
|
|
62
|
+
end
|
|
63
|
+
private_class_method :sort_type
|
|
64
|
+
|
|
65
|
+
def self.validate_sort_element(element, type, index)
|
|
66
|
+
Base.assert_type(element, expected: [NumberValue, StringValue], context: "sort element #{index}")
|
|
67
|
+
return if element.is_a?(type)
|
|
68
|
+
|
|
69
|
+
raise_sort_type_error(type, element.class)
|
|
70
|
+
end
|
|
71
|
+
private_class_method :validate_sort_element
|
|
72
|
+
|
|
73
|
+
def self.raise_sort_type_error(expected_type, actual_type)
|
|
74
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
75
|
+
"Mixed types cannot be sorted",
|
|
76
|
+
expected: expected_type.name,
|
|
77
|
+
actual: actual_type.name,
|
|
78
|
+
context: "sort",
|
|
79
|
+
location: nil
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
private_class_method :raise_sort_type_error
|
|
83
|
+
|
|
84
|
+
def self.slice_indices(start, stop)
|
|
85
|
+
[
|
|
86
|
+
NumericHelpers.non_negative_integer(start, context: "array.slice start"),
|
|
87
|
+
NumericHelpers.non_negative_integer(stop, context: "array.slice stop")
|
|
88
|
+
]
|
|
89
|
+
end
|
|
90
|
+
private_class_method :slice_indices
|
|
91
|
+
|
|
92
|
+
def self.slice_elements(elements, start_index, stop_index)
|
|
93
|
+
length = stop_index - start_index
|
|
94
|
+
return [] if length <= 0 || start_index >= elements.length
|
|
95
|
+
|
|
96
|
+
elements.slice(start_index, length) || []
|
|
97
|
+
end
|
|
98
|
+
private_class_method :slice_elements
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|