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,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
class Evaluator
|
|
6
|
+
# Builds query nodes from user input.
|
|
7
|
+
class QueryNodeBuilder
|
|
8
|
+
# @param query [Object]
|
|
9
|
+
def initialize(query)
|
|
10
|
+
@query = query
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [Object]
|
|
14
|
+
def build
|
|
15
|
+
return query if query.is_a?(AST::Base)
|
|
16
|
+
return reference_from_string if query.is_a?(String)
|
|
17
|
+
|
|
18
|
+
Value.from_ruby(query)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :query
|
|
24
|
+
|
|
25
|
+
def reference_from_string
|
|
26
|
+
base, *path_segments = query.split(".")
|
|
27
|
+
raise EvaluationError.new("Invalid query path: #{query.inspect}", rule: nil, location: nil) if
|
|
28
|
+
base.to_s.empty? || path_segments.any?(&:empty?)
|
|
29
|
+
|
|
30
|
+
AST::Reference.new(
|
|
31
|
+
base: AST::Variable.new(name: base),
|
|
32
|
+
path: path_segments.map { |segment| AST::DotRefArg.new(value: segment) }
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
class Evaluator
|
|
6
|
+
# Resolves reference path segments to Ruby keys.
|
|
7
|
+
class ReferenceKeyResolver
|
|
8
|
+
# @param environment [Environment]
|
|
9
|
+
# @param variable_resolver [#call]
|
|
10
|
+
def initialize(environment:, variable_resolver: nil)
|
|
11
|
+
@environment = environment
|
|
12
|
+
@variable_resolver = variable_resolver
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param segment [Object]
|
|
16
|
+
# @return [Object]
|
|
17
|
+
def resolve(segment)
|
|
18
|
+
resolve_segment(segment)
|
|
19
|
+
rescue ArgumentError
|
|
20
|
+
UndefinedValue.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :environment, :variable_resolver
|
|
26
|
+
|
|
27
|
+
def resolve_segment(segment)
|
|
28
|
+
case segment
|
|
29
|
+
in AST::RefArg[value: value]
|
|
30
|
+
resolve_segment(value)
|
|
31
|
+
in AST::Literal[value:] then value
|
|
32
|
+
in AST::Variable => variable then resolve_variable_key(variable)
|
|
33
|
+
in Value => value then value.to_ruby
|
|
34
|
+
in value then Value.from_ruby(value).to_ruby
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def resolve_variable_key(variable)
|
|
39
|
+
return environment.reference_key_for(variable) unless variable_resolver
|
|
40
|
+
|
|
41
|
+
resolved = variable_resolver.call(variable.name)
|
|
42
|
+
return UndefinedValue.new if resolved.is_a?(UndefinedValue)
|
|
43
|
+
return resolved.to_ruby if resolved.is_a?(Value)
|
|
44
|
+
|
|
45
|
+
Value.from_ruby(resolved).to_ruby
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
class Evaluator
|
|
6
|
+
# Resolves AST references against input/data and rule outputs.
|
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
|
8
|
+
class ReferenceResolver
|
|
9
|
+
UNCACHEABLE = Object.new.freeze
|
|
10
|
+
|
|
11
|
+
# Builds static reference keys for cacheable references.
|
|
12
|
+
class StaticKeyBuilder
|
|
13
|
+
ROOT_NAMES = %w[input data].freeze
|
|
14
|
+
|
|
15
|
+
# @param reference [AST::Reference]
|
|
16
|
+
def initialize(reference)
|
|
17
|
+
@reference = reference
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Array<Object>, nil]
|
|
21
|
+
def call
|
|
22
|
+
base = reference.base
|
|
23
|
+
return nil unless base.is_a?(AST::Variable)
|
|
24
|
+
return nil unless ROOT_NAMES.include?(base.name)
|
|
25
|
+
|
|
26
|
+
keys = [] # @type var keys: Array[Object]
|
|
27
|
+
reference.path.each do |segment|
|
|
28
|
+
key = segment_key(segment)
|
|
29
|
+
return nil unless key
|
|
30
|
+
|
|
31
|
+
keys << key
|
|
32
|
+
end
|
|
33
|
+
keys
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :reference
|
|
39
|
+
|
|
40
|
+
def segment_key(segment)
|
|
41
|
+
value = segment.is_a?(AST::RefArg) ? segment.value : segment
|
|
42
|
+
return value.value if value.is_a?(AST::Literal)
|
|
43
|
+
return value.to_ruby if value.is_a?(Value)
|
|
44
|
+
return value if value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric)
|
|
45
|
+
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param environment [Environment]
|
|
51
|
+
# @param package_path [Array<String>]
|
|
52
|
+
# @param rule_value_provider [RuleValueProvider]
|
|
53
|
+
# @param memoization [Memoization::Store, nil]
|
|
54
|
+
def initialize(environment:, package_path:, rule_value_provider:, imports: [], memoization: nil)
|
|
55
|
+
@environment = environment
|
|
56
|
+
@package_path = package_path
|
|
57
|
+
@rule_value_provider = rule_value_provider
|
|
58
|
+
@memoization = memoization
|
|
59
|
+
@key_resolver = ReferenceKeyResolver.new(
|
|
60
|
+
environment: environment,
|
|
61
|
+
variable_resolver: method(:resolve_variable_key)
|
|
62
|
+
)
|
|
63
|
+
@import_map = build_import_map(imports)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param ref [Object]
|
|
67
|
+
# @return [Value]
|
|
68
|
+
def resolve(ref)
|
|
69
|
+
return environment.resolve_reference(ref) unless ref.is_a?(AST::Reference)
|
|
70
|
+
|
|
71
|
+
cached = cached_reference_value(ref)
|
|
72
|
+
return cached if cached
|
|
73
|
+
|
|
74
|
+
value = resolve_reference_value(ref)
|
|
75
|
+
cache_reference_value(ref, value) if cacheable_reference?(ref)
|
|
76
|
+
value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Resolve an import alias used as a bare variable.
|
|
80
|
+
#
|
|
81
|
+
# @param name [String]
|
|
82
|
+
# @return [Value, nil]
|
|
83
|
+
def resolve_import_variable(name)
|
|
84
|
+
reference = import_map[name.to_s]
|
|
85
|
+
return nil unless reference
|
|
86
|
+
return nil if environment.local_bound?(name)
|
|
87
|
+
return nil unless environment.lookup(name).is_a?(UndefinedValue)
|
|
88
|
+
|
|
89
|
+
resolve(reference)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Resolve a rule name used as a bare variable.
|
|
93
|
+
#
|
|
94
|
+
# @param name [String]
|
|
95
|
+
# @return [Value, nil]
|
|
96
|
+
def resolve_rule_variable(name)
|
|
97
|
+
return nil if environment.local_bound?(name)
|
|
98
|
+
return nil unless environment.lookup(name).is_a?(UndefinedValue)
|
|
99
|
+
return nil unless rule_value_provider.rule_defined?(name)
|
|
100
|
+
|
|
101
|
+
rule_value_provider.value_for(name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Resolve a function call reference to a rule name when possible.
|
|
105
|
+
#
|
|
106
|
+
# @param reference [AST::Reference]
|
|
107
|
+
# @return [String, nil]
|
|
108
|
+
def function_reference_name(reference)
|
|
109
|
+
return nil unless reference.is_a?(AST::Reference)
|
|
110
|
+
|
|
111
|
+
target = function_reference_target(reference)
|
|
112
|
+
return nil unless target
|
|
113
|
+
|
|
114
|
+
resolve_function_reference_name(target)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# :reek:FeatureEnvy
|
|
118
|
+
def function_reference_target(reference)
|
|
119
|
+
import_reference = import_reference_for(reference)
|
|
120
|
+
return reference unless import_reference
|
|
121
|
+
|
|
122
|
+
AST::Reference.new(
|
|
123
|
+
base: import_reference.base,
|
|
124
|
+
path: import_reference.path + reference.path,
|
|
125
|
+
location: reference.location
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# :reek:FeatureEnvy
|
|
130
|
+
def resolve_function_reference_name(reference)
|
|
131
|
+
base = reference.base
|
|
132
|
+
return nil unless base.is_a?(AST::Variable) && base.name == "data"
|
|
133
|
+
|
|
134
|
+
rule_reference(reference.path)&.then do |(rule_name, remaining_path)|
|
|
135
|
+
remaining_path.empty? ? rule_name : nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
attr_reader :environment, :package_path, :rule_value_provider, :key_resolver, :import_map, :memoization
|
|
142
|
+
|
|
143
|
+
def resolve_reference_value(ref)
|
|
144
|
+
import_value = resolve_import_reference(ref)
|
|
145
|
+
return import_value if import_value
|
|
146
|
+
|
|
147
|
+
rule_value = resolve_rule_reference_without_data(ref)
|
|
148
|
+
return rule_value if rule_value
|
|
149
|
+
|
|
150
|
+
base_value = environment.resolve_reference(ref.base)
|
|
151
|
+
resolved = resolve_reference_path_fast(base_value, ref)
|
|
152
|
+
rule_value = resolve_rule_reference(ref)
|
|
153
|
+
rule_value || resolved
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def resolve_rule_reference(ref)
|
|
157
|
+
base = ref.base
|
|
158
|
+
path = ref.path
|
|
159
|
+
return nil unless base.is_a?(AST::Variable) && base.name == "data"
|
|
160
|
+
|
|
161
|
+
rule_reference(path)&.then do |(rule_name, remaining_path)|
|
|
162
|
+
resolve_rule_value(rule_name, remaining_path)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def rule_reference(path)
|
|
167
|
+
keys = valid_reference_keys(path)
|
|
168
|
+
return nil unless keys
|
|
169
|
+
|
|
170
|
+
package_rule_reference(keys, path) || direct_rule_reference(keys, path)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def resolve_rule_value(rule_name, remaining_path)
|
|
174
|
+
value = rule_value_provider.value_for(rule_name)
|
|
175
|
+
return value if remaining_path.empty? || value.undefined?
|
|
176
|
+
|
|
177
|
+
resolve_reference_path(value, remaining_path)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def package_rule_reference(keys, path)
|
|
181
|
+
return nil unless package_match?(keys)
|
|
182
|
+
|
|
183
|
+
prefix_length = package_path.length
|
|
184
|
+
rule_name = keys[prefix_length].to_s
|
|
185
|
+
return nil unless rule_value_provider.rule_defined?(rule_name)
|
|
186
|
+
|
|
187
|
+
[rule_name, path[(prefix_length + 1)..] || []]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def direct_rule_reference(keys, path)
|
|
191
|
+
rule_name = keys.first&.to_s
|
|
192
|
+
return nil unless rule_name && rule_value_provider.rule_defined?(rule_name)
|
|
193
|
+
|
|
194
|
+
[rule_name, path[1..] || []]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def resolve_reference_path(current, path)
|
|
198
|
+
path.each do |segment|
|
|
199
|
+
current = resolve_path_segment(current, segment)
|
|
200
|
+
return current if current.is_a?(UndefinedValue)
|
|
201
|
+
end
|
|
202
|
+
current
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def resolve_reference_path_fast(current, reference)
|
|
206
|
+
keys = static_reference_keys(reference)
|
|
207
|
+
return resolve_reference_path(current, reference.path) unless keys
|
|
208
|
+
|
|
209
|
+
resolve_reference_path_keys(current, keys)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def resolve_reference_path_keys(current, keys)
|
|
213
|
+
keys.each do |key|
|
|
214
|
+
current = current.fetch_reference(key)
|
|
215
|
+
return current if current.is_a?(UndefinedValue)
|
|
216
|
+
end
|
|
217
|
+
current
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def resolve_path_segment(current, segment)
|
|
221
|
+
key = key_resolver.resolve(segment)
|
|
222
|
+
return UndefinedValue.new if key.is_a?(UndefinedValue)
|
|
223
|
+
|
|
224
|
+
current.fetch_reference(key)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def valid_reference_keys(path)
|
|
228
|
+
keys = reference_keys(path)
|
|
229
|
+
keys.any? { |key| key.is_a?(UndefinedValue) } ? nil : keys
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def reference_keys(path)
|
|
233
|
+
path.map { |segment| key_resolver.resolve(segment) }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def package_match?(keys)
|
|
237
|
+
prefix_length = package_path.length
|
|
238
|
+
keys.length > prefix_length && keys[0, prefix_length] == package_path
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def resolve_import_reference(ref)
|
|
242
|
+
import_reference = import_reference_for(ref)
|
|
243
|
+
return nil unless import_reference
|
|
244
|
+
|
|
245
|
+
combined = AST::Reference.new(
|
|
246
|
+
base: import_reference.base,
|
|
247
|
+
path: import_reference.path + ref.path,
|
|
248
|
+
location: ref.location
|
|
249
|
+
)
|
|
250
|
+
resolve(combined)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# rubocop:disable Metrics/AbcSize
|
|
254
|
+
def resolve_rule_reference_without_data(ref)
|
|
255
|
+
base = ref.base
|
|
256
|
+
return nil unless base.is_a?(AST::Variable)
|
|
257
|
+
return nil if environment.local_bound?(base.name)
|
|
258
|
+
return nil unless environment.lookup(base.name).is_a?(UndefinedValue)
|
|
259
|
+
return nil unless rule_value_provider.rule_defined?(base.name)
|
|
260
|
+
|
|
261
|
+
value = rule_value_provider.value_for(base.name)
|
|
262
|
+
return value if ref.path.empty? || value.undefined?
|
|
263
|
+
|
|
264
|
+
resolve_reference_path(value, ref.path)
|
|
265
|
+
end
|
|
266
|
+
# rubocop:enable Metrics/AbcSize
|
|
267
|
+
|
|
268
|
+
# :reek:FeatureEnvy
|
|
269
|
+
def import_reference_for(ref)
|
|
270
|
+
base = ref.base
|
|
271
|
+
return nil unless base.is_a?(AST::Variable)
|
|
272
|
+
return nil if environment.local_bound?(base.name)
|
|
273
|
+
return nil unless environment.lookup(base.name).is_a?(UndefinedValue)
|
|
274
|
+
|
|
275
|
+
import_map[base.name]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def resolve_variable_key(name)
|
|
279
|
+
resolved = environment.lookup(name)
|
|
280
|
+
return resolved unless resolved.is_a?(UndefinedValue)
|
|
281
|
+
return resolved if environment.local_bound?(name)
|
|
282
|
+
|
|
283
|
+
import_value = resolve_import_variable(name)
|
|
284
|
+
return import_value if import_value
|
|
285
|
+
|
|
286
|
+
rule_value = resolve_rule_variable(name)
|
|
287
|
+
rule_value || resolved
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def build_import_map(imports)
|
|
291
|
+
# @type var import_map: Hash[String, AST::Reference]
|
|
292
|
+
import_map = {}
|
|
293
|
+
Array(imports).each_with_object(import_map) do |import, acc|
|
|
294
|
+
path = import_path_segments(import)
|
|
295
|
+
next if path.empty?
|
|
296
|
+
|
|
297
|
+
name = import.alias_name || path.last
|
|
298
|
+
acc[name.to_s] = build_reference_from_path(path)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def import_path_segments(import)
|
|
303
|
+
raw = import.path
|
|
304
|
+
return raw if raw.is_a?(Array)
|
|
305
|
+
return [] if raw.nil?
|
|
306
|
+
|
|
307
|
+
raw.to_s.split(".")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def build_reference_from_path(path)
|
|
311
|
+
base_name, *segments = path
|
|
312
|
+
AST::Reference.new(
|
|
313
|
+
base: AST::Variable.new(name: base_name.to_s),
|
|
314
|
+
path: segments.map { |segment| AST::DotRefArg.new(value: segment.to_s) }
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def cached_reference_value(reference)
|
|
319
|
+
reference_cache&.fetch(reference, nil)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def cache_reference_value(reference, value)
|
|
323
|
+
cache = reference_cache
|
|
324
|
+
return unless cache
|
|
325
|
+
|
|
326
|
+
cache[reference] = value
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def reference_cache
|
|
330
|
+
memoization&.context&.reference_values
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def cacheable_reference?(reference)
|
|
334
|
+
!static_reference_keys(reference).nil?
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def static_reference_keys(reference)
|
|
338
|
+
cache = memoization&.context&.reference_keys
|
|
339
|
+
return StaticKeyBuilder.new(reference).call unless cache
|
|
340
|
+
|
|
341
|
+
cached = cache.fetch(reference) do
|
|
342
|
+
StaticKeyBuilder.new(reference).call || UNCACHEABLE
|
|
343
|
+
end
|
|
344
|
+
return nil if cached == UNCACHEABLE
|
|
345
|
+
|
|
346
|
+
cached
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
# rubocop:enable Metrics/ClassLength
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruby
|
|
4
|
+
module Rego
|
|
5
|
+
class Evaluator
|
|
6
|
+
# Binding helpers for rule evaluation.
|
|
7
|
+
# :reek:DataClump
|
|
8
|
+
class RuleEvaluator
|
|
9
|
+
include BindingHelpers
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# :reek:TooManyStatements
|
|
14
|
+
# :reek:NestedIterators
|
|
15
|
+
def eval_some_decl(literal, _env = environment)
|
|
16
|
+
Enumerator.new do |yielder|
|
|
17
|
+
collection = literal.collection
|
|
18
|
+
next yield_empty_bindings(yielder) unless collection
|
|
19
|
+
|
|
20
|
+
collection_value = expression_evaluator.evaluate(collection)
|
|
21
|
+
next if collection_value.undefined?
|
|
22
|
+
|
|
23
|
+
collection_bindings(literal.variables, collection_value).each { |bindings| yielder << bindings }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# :reek:UtilityFunction
|
|
28
|
+
def yield_empty_bindings(yielder)
|
|
29
|
+
empty_bindings = {} # @type var empty_bindings: Hash[String, Value]
|
|
30
|
+
yielder << empty_bindings
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def each_some_solution(literal)
|
|
34
|
+
eval_some_decl(literal, environment)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def collection_bindings(variables, collection_value)
|
|
38
|
+
case collection_value
|
|
39
|
+
when ArrayValue
|
|
40
|
+
each_array_binding(variables, collection_value)
|
|
41
|
+
when SetValue
|
|
42
|
+
variables.length == 1 ? each_set_binding(variables, collection_value) : empty_bindings_enum
|
|
43
|
+
when ObjectValue
|
|
44
|
+
each_object_binding(variables, collection_value)
|
|
45
|
+
else
|
|
46
|
+
empty_bindings_enum
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# :reek:UtilityFunction
|
|
51
|
+
# :reek:TooManyStatements
|
|
52
|
+
def merge_bindings(existing, additions)
|
|
53
|
+
merged = existing.dup
|
|
54
|
+
additions.each do |name, value|
|
|
55
|
+
current = merged[name]
|
|
56
|
+
return nil if current && current != value
|
|
57
|
+
|
|
58
|
+
merged[name] = value
|
|
59
|
+
end
|
|
60
|
+
merged
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# :reek:UtilityFunction
|
|
64
|
+
def empty_bindings_enum
|
|
65
|
+
Enumerator.new { |yielder| yielder }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|