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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.reek.yml +80 -0
  3. data/.vscode/extensions.json +19 -0
  4. data/.vscode/launch.json +35 -0
  5. data/.vscode/settings.json +25 -0
  6. data/.vscode/tasks.json +117 -0
  7. data/.yardopts +12 -0
  8. data/ARCHITECTURE.md +39 -0
  9. data/CHANGELOG.md +25 -0
  10. data/CODE_OF_CONDUCT.md +10 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +183 -0
  13. data/RELEASING.md +37 -0
  14. data/Rakefile +38 -0
  15. data/SECURITY.md +26 -0
  16. data/Steepfile +10 -0
  17. data/TODO.md +35 -0
  18. data/benchmark/builtin_calls.rb +29 -0
  19. data/benchmark/complex_policy.rb +19 -0
  20. data/benchmark/comprehensions.rb +19 -0
  21. data/benchmark/simple_rules.rb +20 -0
  22. data/examples/README.md +27 -0
  23. data/examples/sample_config.yaml +2 -0
  24. data/examples/simple_policy.rego +7 -0
  25. data/examples/validation_policy.rego +11 -0
  26. data/exe/rego-validate +6 -0
  27. data/lib/ruby/rego/ast/base.rb +95 -0
  28. data/lib/ruby/rego/ast/binary_op.rb +64 -0
  29. data/lib/ruby/rego/ast/call.rb +27 -0
  30. data/lib/ruby/rego/ast/composite.rb +48 -0
  31. data/lib/ruby/rego/ast/comprehension.rb +63 -0
  32. data/lib/ruby/rego/ast/every.rb +37 -0
  33. data/lib/ruby/rego/ast/import.rb +32 -0
  34. data/lib/ruby/rego/ast/literal.rb +70 -0
  35. data/lib/ruby/rego/ast/module.rb +32 -0
  36. data/lib/ruby/rego/ast/package.rb +22 -0
  37. data/lib/ruby/rego/ast/query.rb +63 -0
  38. data/lib/ruby/rego/ast/reference.rb +58 -0
  39. data/lib/ruby/rego/ast/rule.rb +114 -0
  40. data/lib/ruby/rego/ast/unary_op.rb +42 -0
  41. data/lib/ruby/rego/ast/variable.rb +22 -0
  42. data/lib/ruby/rego/ast.rb +17 -0
  43. data/lib/ruby/rego/builtins/aggregates.rb +124 -0
  44. data/lib/ruby/rego/builtins/base.rb +95 -0
  45. data/lib/ruby/rego/builtins/collections/array_ops.rb +103 -0
  46. data/lib/ruby/rego/builtins/collections/object_ops.rb +120 -0
  47. data/lib/ruby/rego/builtins/collections/set_ops.rb +51 -0
  48. data/lib/ruby/rego/builtins/collections.rb +137 -0
  49. data/lib/ruby/rego/builtins/comparisons/casts.rb +139 -0
  50. data/lib/ruby/rego/builtins/comparisons.rb +84 -0
  51. data/lib/ruby/rego/builtins/numeric_helpers.rb +56 -0
  52. data/lib/ruby/rego/builtins/registry.rb +199 -0
  53. data/lib/ruby/rego/builtins/registry_helpers.rb +27 -0
  54. data/lib/ruby/rego/builtins/strings/case_ops.rb +22 -0
  55. data/lib/ruby/rego/builtins/strings/concat.rb +19 -0
  56. data/lib/ruby/rego/builtins/strings/formatting.rb +35 -0
  57. data/lib/ruby/rego/builtins/strings/helpers.rb +62 -0
  58. data/lib/ruby/rego/builtins/strings/number_helpers.rb +48 -0
  59. data/lib/ruby/rego/builtins/strings/search.rb +63 -0
  60. data/lib/ruby/rego/builtins/strings/split.rb +19 -0
  61. data/lib/ruby/rego/builtins/strings/substring.rb +22 -0
  62. data/lib/ruby/rego/builtins/strings/trim.rb +42 -0
  63. data/lib/ruby/rego/builtins/strings/trim_helpers.rb +62 -0
  64. data/lib/ruby/rego/builtins/strings.rb +58 -0
  65. data/lib/ruby/rego/builtins/types.rb +89 -0
  66. data/lib/ruby/rego/call_name.rb +55 -0
  67. data/lib/ruby/rego/cli.rb +1122 -0
  68. data/lib/ruby/rego/compiled_module.rb +114 -0
  69. data/lib/ruby/rego/compiler.rb +1097 -0
  70. data/lib/ruby/rego/environment/overrides.rb +33 -0
  71. data/lib/ruby/rego/environment/reference_resolution.rb +86 -0
  72. data/lib/ruby/rego/environment.rb +230 -0
  73. data/lib/ruby/rego/environment_pool.rb +71 -0
  74. data/lib/ruby/rego/error_handling.rb +58 -0
  75. data/lib/ruby/rego/error_payload.rb +34 -0
  76. data/lib/ruby/rego/errors.rb +196 -0
  77. data/lib/ruby/rego/evaluator/assignment_support.rb +126 -0
  78. data/lib/ruby/rego/evaluator/binding_helpers.rb +60 -0
  79. data/lib/ruby/rego/evaluator/comprehension_evaluator.rb +182 -0
  80. data/lib/ruby/rego/evaluator/expression_dispatch.rb +45 -0
  81. data/lib/ruby/rego/evaluator/expression_evaluator.rb +492 -0
  82. data/lib/ruby/rego/evaluator/object_literal_evaluator.rb +52 -0
  83. data/lib/ruby/rego/evaluator/operator_evaluator.rb +163 -0
  84. data/lib/ruby/rego/evaluator/query_node_builder.rb +38 -0
  85. data/lib/ruby/rego/evaluator/reference_key_resolver.rb +50 -0
  86. data/lib/ruby/rego/evaluator/reference_resolver.rb +352 -0
  87. data/lib/ruby/rego/evaluator/rule_evaluator/bindings.rb +70 -0
  88. data/lib/ruby/rego/evaluator/rule_evaluator.rb +550 -0
  89. data/lib/ruby/rego/evaluator/rule_value_provider.rb +56 -0
  90. data/lib/ruby/rego/evaluator/variable_collector.rb +221 -0
  91. data/lib/ruby/rego/evaluator.rb +174 -0
  92. data/lib/ruby/rego/lexer/number_reader.rb +68 -0
  93. data/lib/ruby/rego/lexer/stream.rb +137 -0
  94. data/lib/ruby/rego/lexer/string_reader.rb +90 -0
  95. data/lib/ruby/rego/lexer/template_string_reader.rb +62 -0
  96. data/lib/ruby/rego/lexer.rb +206 -0
  97. data/lib/ruby/rego/location.rb +73 -0
  98. data/lib/ruby/rego/memoization.rb +67 -0
  99. data/lib/ruby/rego/parser/collections.rb +173 -0
  100. data/lib/ruby/rego/parser/expressions.rb +216 -0
  101. data/lib/ruby/rego/parser/precedence.rb +42 -0
  102. data/lib/ruby/rego/parser/query.rb +139 -0
  103. data/lib/ruby/rego/parser/references.rb +115 -0
  104. data/lib/ruby/rego/parser/rules.rb +310 -0
  105. data/lib/ruby/rego/parser.rb +210 -0
  106. data/lib/ruby/rego/policy.rb +50 -0
  107. data/lib/ruby/rego/result.rb +91 -0
  108. data/lib/ruby/rego/token.rb +206 -0
  109. data/lib/ruby/rego/unifier.rb +451 -0
  110. data/lib/ruby/rego/value.rb +379 -0
  111. data/lib/ruby/rego/version.rb +7 -0
  112. data/lib/ruby/rego/with_modifiers/with_modifier.rb +37 -0
  113. data/lib/ruby/rego/with_modifiers/with_modifier_applier.rb +48 -0
  114. data/lib/ruby/rego/with_modifiers/with_modifier_builtin_override.rb +128 -0
  115. data/lib/ruby/rego/with_modifiers/with_modifier_context.rb +120 -0
  116. data/lib/ruby/rego/with_modifiers/with_modifier_path_key_resolver.rb +42 -0
  117. data/lib/ruby/rego/with_modifiers/with_modifier_path_override.rb +99 -0
  118. data/lib/ruby/rego/with_modifiers/with_modifier_root_scope.rb +58 -0
  119. data/lib/ruby/rego.rb +72 -0
  120. data/sig/objspace.rbs +4 -0
  121. data/sig/psych.rbs +7 -0
  122. data/sig/rego_validate.rbs +382 -0
  123. data/sig/ruby/rego.rbs +2150 -0
  124. 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