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,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../errors"
|
|
5
|
+
require_relative "../../value"
|
|
6
|
+
|
|
7
|
+
module Ruby
|
|
8
|
+
module Rego
|
|
9
|
+
module Builtins
|
|
10
|
+
module Collections
|
|
11
|
+
# Object-focused collection helpers.
|
|
12
|
+
module ObjectOps
|
|
13
|
+
# @param object [Ruby::Rego::Value]
|
|
14
|
+
# @param key [Ruby::Rego::Value]
|
|
15
|
+
# @param default [Ruby::Rego::Value]
|
|
16
|
+
# @return [Ruby::Rego::Value]
|
|
17
|
+
def self.object_get(object, key, default)
|
|
18
|
+
obj = object_value(object, name: "object.get object")
|
|
19
|
+
key_value = normalize_object_key(Base.to_ruby(key))
|
|
20
|
+
value = obj.fetch_reference(key_value)
|
|
21
|
+
value.is_a?(UndefinedValue) ? default : value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param object [Ruby::Rego::Value]
|
|
25
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
26
|
+
def self.object_keys(object)
|
|
27
|
+
obj = object_value(object, name: "object.keys")
|
|
28
|
+
ArrayValue.new(obj.value.keys)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param object [Ruby::Rego::Value]
|
|
32
|
+
# @param keys [Ruby::Rego::Value]
|
|
33
|
+
# @return [Ruby::Rego::ObjectValue]
|
|
34
|
+
def self.object_remove(object, keys)
|
|
35
|
+
obj = object_value(object, name: "object.remove object")
|
|
36
|
+
remove_keys = key_collection(keys, name: "object.remove keys")
|
|
37
|
+
filtered = obj.value.reject { |key, _value| remove_keys.include?(normalize_object_key(key)) }
|
|
38
|
+
ObjectValue.new(filtered)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param left [Ruby::Rego::Value]
|
|
42
|
+
# @param right [Ruby::Rego::Value]
|
|
43
|
+
# @return [Ruby::Rego::ObjectValue]
|
|
44
|
+
def self.union_objects(left, right)
|
|
45
|
+
left_obj = object_value(left, name: "union left").value
|
|
46
|
+
right_obj = object_value(right, name: "union right").value
|
|
47
|
+
ObjectValue.new(merge_objects(left_obj, right_obj))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.object_value(value, name:)
|
|
51
|
+
object = value # @type var object: ObjectValue
|
|
52
|
+
Base.assert_type(object, expected: ObjectValue, context: name)
|
|
53
|
+
object
|
|
54
|
+
end
|
|
55
|
+
private_class_method :object_value
|
|
56
|
+
|
|
57
|
+
def self.normalize_object_key(key)
|
|
58
|
+
key.is_a?(Symbol) ? key.to_s : key
|
|
59
|
+
end
|
|
60
|
+
private_class_method :normalize_object_key
|
|
61
|
+
|
|
62
|
+
def self.key_collection(keys, name:)
|
|
63
|
+
values = key_values(keys, name: name)
|
|
64
|
+
Set.new(values.map { |key| normalize_object_key(Base.to_ruby(key)) })
|
|
65
|
+
end
|
|
66
|
+
private_class_method :key_collection
|
|
67
|
+
|
|
68
|
+
def self.key_values(keys, name:)
|
|
69
|
+
return array_key_values(keys) if keys.is_a?(ArrayValue)
|
|
70
|
+
return values_from_set(keys) if keys.is_a?(SetValue)
|
|
71
|
+
|
|
72
|
+
Base.assert_type(keys, expected: [ArrayValue, SetValue], context: name)
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
private_class_method :key_values
|
|
76
|
+
|
|
77
|
+
def self.array_key_values(keys)
|
|
78
|
+
keys.value
|
|
79
|
+
end
|
|
80
|
+
private_class_method :array_key_values
|
|
81
|
+
|
|
82
|
+
def self.values_from_set(keys)
|
|
83
|
+
keys.value.to_a
|
|
84
|
+
end
|
|
85
|
+
private_class_method :values_from_set
|
|
86
|
+
|
|
87
|
+
def self.merge_objects(left_obj, right_obj)
|
|
88
|
+
conflict = conflicting_key(left_obj, right_obj)
|
|
89
|
+
raise_object_conflict(conflict, left_obj, right_obj) if conflict
|
|
90
|
+
|
|
91
|
+
left_obj.merge(right_obj)
|
|
92
|
+
end
|
|
93
|
+
private_class_method :merge_objects
|
|
94
|
+
|
|
95
|
+
def self.conflicting_key(left_obj, right_obj)
|
|
96
|
+
left_obj.each_key do |key|
|
|
97
|
+
next unless right_obj.key?(key)
|
|
98
|
+
next if left_obj[key] == right_obj[key]
|
|
99
|
+
|
|
100
|
+
return key
|
|
101
|
+
end
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
private_class_method :conflicting_key
|
|
105
|
+
|
|
106
|
+
def self.raise_object_conflict(key, left_obj, right_obj)
|
|
107
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
108
|
+
"Conflicting object keys",
|
|
109
|
+
expected: "matching values for key #{key.inspect}",
|
|
110
|
+
actual: [left_obj[key].to_ruby, right_obj[key].to_ruby],
|
|
111
|
+
context: "union",
|
|
112
|
+
location: nil
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
private_class_method :raise_object_conflict
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../errors"
|
|
5
|
+
require_relative "../../value"
|
|
6
|
+
|
|
7
|
+
module Ruby
|
|
8
|
+
module Rego
|
|
9
|
+
module Builtins
|
|
10
|
+
module Collections
|
|
11
|
+
# Set-focused collection helpers.
|
|
12
|
+
module SetOps
|
|
13
|
+
# @param left [Ruby::Rego::Value]
|
|
14
|
+
# @param right [Ruby::Rego::Value]
|
|
15
|
+
# @return [Ruby::Rego::SetValue]
|
|
16
|
+
def self.intersection(left, right)
|
|
17
|
+
set_operation(left, right, name: "intersection") { |left_set, right_set| left_set & right_set }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param left [Ruby::Rego::Value]
|
|
21
|
+
# @param right [Ruby::Rego::Value]
|
|
22
|
+
# @return [Ruby::Rego::SetValue]
|
|
23
|
+
def self.set_diff(left, right)
|
|
24
|
+
set_operation(left, right, name: "set_diff") { |left_set, right_set| left_set - right_set }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param left [Ruby::Rego::Value]
|
|
28
|
+
# @param right [Ruby::Rego::Value]
|
|
29
|
+
# @return [Ruby::Rego::SetValue]
|
|
30
|
+
def self.union_sets(left, right)
|
|
31
|
+
set_operation(left, right, name: "union") { |left_set, right_set| left_set | right_set }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.set_operation(left, right, name:)
|
|
35
|
+
left_set = set_contents(left, name: "#{name} left")
|
|
36
|
+
right_set = set_contents(right, name: "#{name} right")
|
|
37
|
+
SetValue.new(yield(left_set, right_set))
|
|
38
|
+
end
|
|
39
|
+
private_class_method :set_operation
|
|
40
|
+
|
|
41
|
+
def self.set_contents(value, name:)
|
|
42
|
+
set_contents = value # @type var set_contents: SetValue
|
|
43
|
+
Base.assert_type(set_contents, expected: SetValue, context: name)
|
|
44
|
+
set_contents.value
|
|
45
|
+
end
|
|
46
|
+
private_class_method :set_contents
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "registry"
|
|
5
|
+
require_relative "registry_helpers"
|
|
6
|
+
require_relative "../errors"
|
|
7
|
+
require_relative "../value"
|
|
8
|
+
require_relative "collections/array_ops"
|
|
9
|
+
require_relative "collections/object_ops"
|
|
10
|
+
require_relative "collections/set_ops"
|
|
11
|
+
|
|
12
|
+
module Ruby
|
|
13
|
+
module Rego
|
|
14
|
+
module Builtins
|
|
15
|
+
# Built-in collection helpers.
|
|
16
|
+
module Collections
|
|
17
|
+
extend RegistryHelpers
|
|
18
|
+
|
|
19
|
+
MISSING_SET_ARGUMENT = Object.new.freeze
|
|
20
|
+
|
|
21
|
+
COLLECTION_FUNCTIONS = {
|
|
22
|
+
"set" => { arity: [0, 1], handler: :set },
|
|
23
|
+
"sort" => { arity: 1, handler: :sort },
|
|
24
|
+
"array.concat" => { arity: 2, handler: :array_concat },
|
|
25
|
+
"array.slice" => { arity: 3, handler: :array_slice },
|
|
26
|
+
"object.get" => { arity: 3, handler: :object_get },
|
|
27
|
+
"object.keys" => { arity: 1, handler: :object_keys },
|
|
28
|
+
"object.remove" => { arity: 2, handler: :object_remove },
|
|
29
|
+
"union" => { arity: 2, handler: :union },
|
|
30
|
+
"intersection" => { arity: 2, handler: :intersection },
|
|
31
|
+
"set_diff" => { arity: 2, handler: :set_diff }
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
# @return [Ruby::Rego::Builtins::BuiltinRegistry]
|
|
35
|
+
def self.register!
|
|
36
|
+
registry = BuiltinRegistry.instance
|
|
37
|
+
|
|
38
|
+
register_configured_functions(registry, COLLECTION_FUNCTIONS)
|
|
39
|
+
|
|
40
|
+
registry
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private_class_method :register_configured_functions, :register_configured_function
|
|
44
|
+
|
|
45
|
+
# @param array [Ruby::Rego::Value]
|
|
46
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
47
|
+
def self.sort(array)
|
|
48
|
+
ArrayOps.sort(array)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param value [Ruby::Rego::Value, nil]
|
|
52
|
+
# @return [Ruby::Rego::SetValue]
|
|
53
|
+
def self.set(value = MISSING_SET_ARGUMENT)
|
|
54
|
+
return SetValue.new([]) if value.equal?(MISSING_SET_ARGUMENT)
|
|
55
|
+
|
|
56
|
+
Base.assert_type(value, expected: [ArrayValue, SetValue], context: "set")
|
|
57
|
+
return value if value.is_a?(SetValue)
|
|
58
|
+
|
|
59
|
+
SetValue.new(value.value)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param left [Ruby::Rego::Value]
|
|
63
|
+
# @param right [Ruby::Rego::Value]
|
|
64
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
65
|
+
def self.array_concat(left, right)
|
|
66
|
+
ArrayOps.array_concat(left, right)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param array [Ruby::Rego::Value]
|
|
70
|
+
# @param start [Ruby::Rego::Value]
|
|
71
|
+
# @param stop [Ruby::Rego::Value]
|
|
72
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
73
|
+
def self.array_slice(array, start, stop)
|
|
74
|
+
ArrayOps.array_slice(array, start, stop)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @param object [Ruby::Rego::Value]
|
|
78
|
+
# @param key [Ruby::Rego::Value]
|
|
79
|
+
# @param default [Ruby::Rego::Value]
|
|
80
|
+
# @return [Ruby::Rego::Value]
|
|
81
|
+
def self.object_get(object, key, default)
|
|
82
|
+
ObjectOps.object_get(object, key, default)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param object [Ruby::Rego::Value]
|
|
86
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
87
|
+
def self.object_keys(object)
|
|
88
|
+
ObjectOps.object_keys(object)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param object [Ruby::Rego::Value]
|
|
92
|
+
# @param keys [Ruby::Rego::Value]
|
|
93
|
+
# @return [Ruby::Rego::ObjectValue]
|
|
94
|
+
def self.object_remove(object, keys)
|
|
95
|
+
ObjectOps.object_remove(object, keys)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param left [Ruby::Rego::Value]
|
|
99
|
+
# @param right [Ruby::Rego::Value]
|
|
100
|
+
# @return [Ruby::Rego::Value]
|
|
101
|
+
def self.union(left, right)
|
|
102
|
+
return SetOps.union_sets(left, right) if left.is_a?(SetValue) && right.is_a?(SetValue)
|
|
103
|
+
return ObjectOps.union_objects(left, right) if left.is_a?(ObjectValue) && right.is_a?(ObjectValue)
|
|
104
|
+
|
|
105
|
+
raise_union_type_error(left, right)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @param left [Ruby::Rego::Value]
|
|
109
|
+
# @param right [Ruby::Rego::Value]
|
|
110
|
+
# @return [Ruby::Rego::SetValue]
|
|
111
|
+
def self.intersection(left, right)
|
|
112
|
+
SetOps.intersection(left, right)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param left [Ruby::Rego::Value]
|
|
116
|
+
# @param right [Ruby::Rego::Value]
|
|
117
|
+
# @return [Ruby::Rego::SetValue]
|
|
118
|
+
def self.set_diff(left, right)
|
|
119
|
+
SetOps.set_diff(left, right)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.raise_union_type_error(left, right)
|
|
123
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
124
|
+
"Type mismatch",
|
|
125
|
+
expected: "both sets or both objects",
|
|
126
|
+
actual: "#{left.class.name} and #{right.class.name}",
|
|
127
|
+
context: "union",
|
|
128
|
+
location: nil
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
private_class_method :raise_union_type_error
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
Ruby::Rego::Builtins::Collections.register!
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../errors"
|
|
5
|
+
require_relative "../../value"
|
|
6
|
+
|
|
7
|
+
module Ruby
|
|
8
|
+
module Rego
|
|
9
|
+
module Builtins
|
|
10
|
+
module Comparisons
|
|
11
|
+
# Casting and conversion helpers.
|
|
12
|
+
module Casts
|
|
13
|
+
# @param value [Ruby::Rego::Value]
|
|
14
|
+
# @return [Ruby::Rego::NumberValue]
|
|
15
|
+
def self.to_number(value)
|
|
16
|
+
raw = value.value
|
|
17
|
+
return NumberValue.new(raw) if value.is_a?(NumberValue)
|
|
18
|
+
return NumberValue.new(number_from_string(raw)) if value.is_a?(StringValue)
|
|
19
|
+
|
|
20
|
+
raise_type_mismatch("to_number", "number or string", value.class.name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param value [Ruby::Rego::Value]
|
|
24
|
+
# @return [Ruby::Rego::StringValue]
|
|
25
|
+
def self.cast_string(value)
|
|
26
|
+
Base.assert_type(
|
|
27
|
+
value,
|
|
28
|
+
expected: [StringValue, NumberValue, BooleanValue, NullValue],
|
|
29
|
+
context: "cast_string"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return value if value.is_a?(StringValue)
|
|
33
|
+
return StringValue.new("null") if value.is_a?(NullValue)
|
|
34
|
+
|
|
35
|
+
StringValue.new(Base.to_ruby(value).to_s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param value [Ruby::Rego::Value]
|
|
39
|
+
# @return [Ruby::Rego::BooleanValue]
|
|
40
|
+
def self.cast_boolean(value)
|
|
41
|
+
return value if value.is_a?(BooleanValue)
|
|
42
|
+
|
|
43
|
+
raw = value.value
|
|
44
|
+
return boolean_from_string(raw) if value.is_a?(StringValue)
|
|
45
|
+
return boolean_from_number(raw) if value.is_a?(NumberValue)
|
|
46
|
+
|
|
47
|
+
raise_type_mismatch("cast_boolean", "boolean, string, or number", value.class.name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param value [Ruby::Rego::Value]
|
|
51
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
52
|
+
def self.cast_array(value)
|
|
53
|
+
return value if value.is_a?(ArrayValue)
|
|
54
|
+
|
|
55
|
+
Base.assert_type(value, expected: [ArrayValue, SetValue], context: "cast_array")
|
|
56
|
+
ArrayValue.new(value.value.to_a)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param value [Ruby::Rego::Value]
|
|
60
|
+
# @return [Ruby::Rego::SetValue]
|
|
61
|
+
def self.cast_set(value)
|
|
62
|
+
return value if value.is_a?(SetValue)
|
|
63
|
+
|
|
64
|
+
Base.assert_type(value, expected: [SetValue, ArrayValue], context: "cast_set")
|
|
65
|
+
SetValue.new(value.value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param value [Ruby::Rego::Value]
|
|
69
|
+
# @return [Ruby::Rego::ObjectValue]
|
|
70
|
+
def self.cast_object(value)
|
|
71
|
+
object = value # @type var object: ObjectValue
|
|
72
|
+
Base.assert_type(object, expected: ObjectValue, context: "cast_object")
|
|
73
|
+
object
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.number_from_string(text)
|
|
77
|
+
Integer(text, 10)
|
|
78
|
+
rescue ArgumentError
|
|
79
|
+
float = Float(text, exception: false)
|
|
80
|
+
return float if float&.finite?
|
|
81
|
+
|
|
82
|
+
raise_number_error(text)
|
|
83
|
+
end
|
|
84
|
+
private_class_method :number_from_string
|
|
85
|
+
|
|
86
|
+
def self.raise_number_error(text)
|
|
87
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
88
|
+
"Invalid number string",
|
|
89
|
+
expected: "numeric string",
|
|
90
|
+
actual: text,
|
|
91
|
+
context: "to_number",
|
|
92
|
+
location: nil
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
private_class_method :raise_number_error
|
|
96
|
+
|
|
97
|
+
def self.boolean_from_string(text)
|
|
98
|
+
normalized = text.strip.downcase
|
|
99
|
+
return BooleanValue.new(true) if normalized == "true"
|
|
100
|
+
return BooleanValue.new(false) if normalized == "false"
|
|
101
|
+
|
|
102
|
+
raise_cast_error("Expected boolean string", "cast_boolean", text)
|
|
103
|
+
end
|
|
104
|
+
private_class_method :boolean_from_string
|
|
105
|
+
|
|
106
|
+
def self.boolean_from_number(number)
|
|
107
|
+
return BooleanValue.new(false) if number.zero?
|
|
108
|
+
return BooleanValue.new(true) if number == 1
|
|
109
|
+
|
|
110
|
+
raise_cast_error("Expected 0 or 1 for boolean cast", "cast_boolean", number)
|
|
111
|
+
end
|
|
112
|
+
private_class_method :boolean_from_number
|
|
113
|
+
|
|
114
|
+
def self.raise_cast_error(message, context, actual)
|
|
115
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
116
|
+
message,
|
|
117
|
+
expected: "castable value",
|
|
118
|
+
actual: actual,
|
|
119
|
+
context: context,
|
|
120
|
+
location: nil
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
private_class_method :raise_cast_error
|
|
124
|
+
|
|
125
|
+
def self.raise_type_mismatch(context, expected, actual)
|
|
126
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
127
|
+
"Type mismatch",
|
|
128
|
+
expected: expected,
|
|
129
|
+
actual: actual,
|
|
130
|
+
context: context,
|
|
131
|
+
location: nil
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
private_class_method :raise_type_mismatch
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "registry"
|
|
5
|
+
require_relative "registry_helpers"
|
|
6
|
+
require_relative "../value"
|
|
7
|
+
require_relative "comparisons/casts"
|
|
8
|
+
|
|
9
|
+
module Ruby
|
|
10
|
+
module Rego
|
|
11
|
+
module Builtins
|
|
12
|
+
# Built-in comparison and casting helpers.
|
|
13
|
+
module Comparisons
|
|
14
|
+
extend RegistryHelpers
|
|
15
|
+
|
|
16
|
+
COMPARISON_FUNCTIONS = {
|
|
17
|
+
"equal" => { arity: 2, handler: :equal },
|
|
18
|
+
"to_number" => { arity: 1, handler: :to_number },
|
|
19
|
+
"cast_string" => { arity: 1, handler: :cast_string },
|
|
20
|
+
"cast_boolean" => { arity: 1, handler: :cast_boolean },
|
|
21
|
+
"cast_array" => { arity: 1, handler: :cast_array },
|
|
22
|
+
"cast_set" => { arity: 1, handler: :cast_set },
|
|
23
|
+
"cast_object" => { arity: 1, handler: :cast_object }
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# @return [Ruby::Rego::Builtins::BuiltinRegistry]
|
|
27
|
+
def self.register!
|
|
28
|
+
registry = BuiltinRegistry.instance
|
|
29
|
+
|
|
30
|
+
register_configured_functions(registry, COMPARISON_FUNCTIONS)
|
|
31
|
+
|
|
32
|
+
registry
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private_class_method :register_configured_functions, :register_configured_function
|
|
36
|
+
|
|
37
|
+
# @param left [Ruby::Rego::Value]
|
|
38
|
+
# @param right [Ruby::Rego::Value]
|
|
39
|
+
# @return [Ruby::Rego::BooleanValue]
|
|
40
|
+
def self.equal(left, right)
|
|
41
|
+
BooleanValue.new(left == right)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param value [Ruby::Rego::Value]
|
|
45
|
+
# @return [Ruby::Rego::NumberValue]
|
|
46
|
+
def self.to_number(value)
|
|
47
|
+
Casts.to_number(value)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param value [Ruby::Rego::Value]
|
|
51
|
+
# @return [Ruby::Rego::StringValue]
|
|
52
|
+
def self.cast_string(value)
|
|
53
|
+
Casts.cast_string(value)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param value [Ruby::Rego::Value]
|
|
57
|
+
# @return [Ruby::Rego::BooleanValue]
|
|
58
|
+
def self.cast_boolean(value)
|
|
59
|
+
Casts.cast_boolean(value)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param value [Ruby::Rego::Value]
|
|
63
|
+
# @return [Ruby::Rego::ArrayValue]
|
|
64
|
+
def self.cast_array(value)
|
|
65
|
+
Casts.cast_array(value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param value [Ruby::Rego::Value]
|
|
69
|
+
# @return [Ruby::Rego::SetValue]
|
|
70
|
+
def self.cast_set(value)
|
|
71
|
+
Casts.cast_set(value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @param value [Ruby::Rego::Value]
|
|
75
|
+
# @return [Ruby::Rego::ObjectValue]
|
|
76
|
+
def self.cast_object(value)
|
|
77
|
+
Casts.cast_object(value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Ruby::Rego::Builtins::Comparisons.register!
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../errors"
|
|
5
|
+
require_relative "../value"
|
|
6
|
+
|
|
7
|
+
module Ruby
|
|
8
|
+
module Rego
|
|
9
|
+
module Builtins
|
|
10
|
+
# Shared numeric coercion helpers for builtins.
|
|
11
|
+
module NumericHelpers
|
|
12
|
+
# @param value [Ruby::Rego::Value]
|
|
13
|
+
# @param context [String]
|
|
14
|
+
# @return [Integer]
|
|
15
|
+
def self.integer_value(value, context:)
|
|
16
|
+
Base.assert_type(value, expected: NumberValue, context: context)
|
|
17
|
+
numeric = value.value
|
|
18
|
+
return numeric if numeric.is_a?(Integer)
|
|
19
|
+
return numeric.to_i if numeric.is_a?(Float) && numeric.finite? && numeric.modulo(1).zero?
|
|
20
|
+
|
|
21
|
+
raise_integer_error(numeric, context)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param value [Ruby::Rego::Value]
|
|
25
|
+
# @param context [String]
|
|
26
|
+
# @return [Integer]
|
|
27
|
+
def self.non_negative_integer(value, context:)
|
|
28
|
+
integer = integer_value(value, context: context)
|
|
29
|
+
return integer if integer >= 0
|
|
30
|
+
|
|
31
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
32
|
+
"Expected non-negative integer",
|
|
33
|
+
expected: "non-negative integer",
|
|
34
|
+
actual: integer,
|
|
35
|
+
context: context,
|
|
36
|
+
location: nil
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param numeric [Numeric]
|
|
41
|
+
# @param context [String]
|
|
42
|
+
# @return [void]
|
|
43
|
+
def self.raise_integer_error(numeric, context)
|
|
44
|
+
raise Ruby::Rego::BuiltinArgumentError.new(
|
|
45
|
+
"Expected integer",
|
|
46
|
+
expected: "integer",
|
|
47
|
+
actual: numeric,
|
|
48
|
+
context: context,
|
|
49
|
+
location: nil
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
private_class_method :raise_integer_error
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|