Class: Ruby::Rego::Environment
- Inherits:
-
Object
- Object
- Ruby::Rego::Environment
- Defined in:
- lib/ruby/rego/environment.rb
Overview
Execution environment for evaluating Rego policies. :reek:TooManyInstanceVariables :reek:TooManyMethods rubocop:disable Metrics/ClassLength
Defined Under Namespace
Classes: State
Constant Summary collapse
- RESERVED_BINDINGS =
{ "input" => :input, "data" => :data }.freeze
- RESERVED_NAMES =
RESERVED_BINDINGS.keys.freeze
Constants included from EnvironmentOverrides
Ruby::Rego::EnvironmentOverrides::UNSET
Instance Attribute Summary collapse
-
#builtin_registry ⇒ Builtins::BuiltinRegistry, Builtins::BuiltinRegistryOverlay
readonly
Builtin registry in use.
-
#data ⇒ Value
readonly
Data document as a Rego value.
-
#input ⇒ Value
readonly
Input document as a Rego value.
-
#memoization ⇒ Memoization::Store
readonly
Memoization store for evaluation caches.
-
#rules ⇒ Hash
readonly
Rule index by name.
Class Method Summary collapse
-
.from_state(state) ⇒ Environment
Build an environment from a state struct.
Instance Method Summary collapse
-
#bind(name, value) ⇒ Value
Bind a local name to a value.
-
#data_overrides? ⇒ bool
Whether any
withdata override is active. -
#data_path_overridden?(keys) ⇒ bool
Whether
keys(a data reference’s key path) was shadowed by awithdata override, so the data-tree value must take precedence over any rule at that path. -
#initialize(input: {}, data: {}, rules: {}, builtin_registry: Builtins::BuiltinRegistry.instance) ⇒ Environment
constructor
Create an evaluation environment.
-
#local_bound?(name) ⇒ Boolean
Check whether a name is bound in any local scope.
-
#lookup(name) ⇒ Value
Lookup a binding from the current scope chain.
-
#pop_scope ⇒ void
Pop the latest local scope.
-
#prepare_for_pool ⇒ Environment
Reset the environment for pool reuse.
-
#push_scope ⇒ Environment
Push a new scope for local bindings.
-
#reset(state) ⇒ Environment
Reset environment state for reuse without mutation semantics.
-
#reset!(state) ⇒ Environment
Reset environment state for reuse.
-
#using_builtin_registry(registry) ⇒ Object
Execute a block with an overridden builtin registry as the base for the whole evaluation, WITHOUT opening a memoization context.
-
#with_bindings(bindings) ⇒ Object
Execute a block with additional temporary bindings.
-
#with_builtin_registry(registry) {|environment| ... } ⇒ Object
Execute a block with an overridden builtin registry.
Methods included from EnvironmentReferenceResolution
#reference_key_for, #resolve_reference
Methods included from EnvironmentOverrides
Constructor Details
#initialize(input: {}, data: {}, rules: {}, builtin_registry: Builtins::BuiltinRegistry.instance) ⇒ Environment
Create an evaluation environment.
:reek:TooManyStatements
59 60 61 62 63 64 65 66 |
# File 'lib/ruby/rego/environment.rb', line 59 def initialize(input: {}, data: {}, rules: {}, builtin_registry: Builtins::BuiltinRegistry.instance) @memoization = Memoization::Store.new @builtin_registry = builtin_registry @locals = [fresh_scope] # @type var locals: Array[Hash[String, Value]] @scope_pool = [] # @type var @scope_pool: Array[Hash[String, Value]] @overridden_data_paths = [] apply_state(State.new(input: input, data: data, rules: rules, builtin_registry: builtin_registry)) end |
Instance Attribute Details
#builtin_registry ⇒ Builtins::BuiltinRegistry, Builtins::BuiltinRegistryOverlay (readonly)
Builtin registry in use.
45 46 47 |
# File 'lib/ruby/rego/environment.rb', line 45 def builtin_registry @builtin_registry end |
#data ⇒ Value (readonly)
Data document as a Rego value.
35 36 37 |
# File 'lib/ruby/rego/environment.rb', line 35 def data @data end |
#input ⇒ Value (readonly)
Input document as a Rego value.
30 31 32 |
# File 'lib/ruby/rego/environment.rb', line 30 def input @input end |
#memoization ⇒ Memoization::Store (readonly)
Memoization store for evaluation caches.
50 51 52 |
# File 'lib/ruby/rego/environment.rb', line 50 def memoization @memoization end |
#rules ⇒ Hash (readonly)
Rule index by name.
40 41 42 |
# File 'lib/ruby/rego/environment.rb', line 40 def rules @rules end |
Class Method Details
.from_state(state) ⇒ Environment
Build an environment from a state struct.
92 93 94 95 96 97 98 99 |
# File 'lib/ruby/rego/environment.rb', line 92 def self.from_state(state) new( input: state.input, data: state.data, rules: state.rules, builtin_registry: state.builtin_registry ) end |
Instance Method Details
#bind(name, value) ⇒ Value
Bind a local name to a value.
159 160 161 162 163 164 165 166 |
# File 'lib/ruby/rego/environment.rb', line 159 def bind(name, value) name = name.to_s raise Error, "Cannot bind reserved name: #{name}" if RESERVED_NAMES.include?(name) value = Value.from_ruby(value) locals.last[name] = value value end |
#data_overrides? ⇒ bool
Whether any with data override is active. Cheap guard for the common
no-with path so callers can skip building a key list.
72 73 74 |
# File 'lib/ruby/rego/environment.rb', line 72 def data_overrides? @overridden_data_paths.any? end |
#data_path_overridden?(keys) ⇒ bool
Whether keys (a data reference’s key path) was shadowed by a with
data override, so the data-tree value must take precedence over any rule
at that path. True when a tracked override path equals or prefixes keys.
82 83 84 85 86 |
# File 'lib/ruby/rego/environment.rb', line 82 def data_path_overridden?(keys) return false unless keys @overridden_data_paths.any? { |path| keys[0, path.length] == path } end |
#local_bound?(name) ⇒ Boolean
Check whether a name is bound in any local scope.
189 190 191 192 193 194 195 196 197 198 |
# File 'lib/ruby/rego/environment.rb', line 189 def local_bound?(name) name = name.to_s return false if RESERVED_NAMES.include?(name) locals.reverse_each do |scope| return true if scope.key?(name) end false end |
#lookup(name) ⇒ Value
Lookup a binding from the current scope chain.
:reek:TooManyStatements
173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/ruby/rego/environment.rb', line 173 def lookup(name) name = name.to_s reserved = RESERVED_BINDINGS[name] return public_send(reserved) if reserved locals.reverse_each do |scope| return scope[name] if scope.key?(name) end UndefinedValue.new end |
#pop_scope ⇒ void
This method returns an undefined value.
Pop the latest local scope.
118 119 120 121 122 123 124 125 |
# File 'lib/ruby/rego/environment.rb', line 118 def pop_scope return nil if locals.length <= 1 scope = locals.pop # @type var scope: Hash[String, Value] scope.clear scope_pool << scope nil end |
#prepare_for_pool ⇒ Environment
Reset the environment for pool reuse.
149 150 151 152 |
# File 'lib/ruby/rego/environment.rb', line 149 def prepare_for_pool empty_hash = {} # @type var empty_hash: Hash[untyped, untyped] reset(State.new(input: empty_hash, data: empty_hash, rules: rules, builtin_registry: builtin_registry)) end |
#push_scope ⇒ Environment
Push a new scope for local bindings.
107 108 109 110 111 112 113 |
# File 'lib/ruby/rego/environment.rb', line 107 def push_scope scope = scope_pool.pop scope ||= fresh_scope scope.clear locals << scope self end |
#reset(state) ⇒ Environment
Reset environment state for reuse without mutation semantics.
142 143 144 |
# File 'lib/ruby/rego/environment.rb', line 142 def reset(state) reset!(state) end |
#reset!(state) ⇒ Environment
Reset environment state for reuse.
131 132 133 134 135 136 |
# File 'lib/ruby/rego/environment.rb', line 131 def reset!(state) apply_state(state) reset_scopes memoization.reset! self end |
#using_builtin_registry(registry) ⇒ Object
Execute a block with an overridden builtin registry as the base for the whole evaluation,
WITHOUT opening a memoization context. Unlike with_builtin_registry (used by with
modifiers mid-evaluation, where cache isolation from the surrounding scope is required),
the caller here has just reset memoization, so the evaluation runs in that clean base
context rather than a redundant nested one.
236 237 238 239 240 241 242 |
# File 'lib/ruby/rego/environment.rb', line 236 def using_builtin_registry(registry) original = @builtin_registry @builtin_registry = registry yield ensure @builtin_registry = original end |
#with_bindings(bindings) ⇒ Object
Execute a block with additional temporary bindings.
205 206 207 208 209 210 211 |
# File 'lib/ruby/rego/environment.rb', line 205 def with_bindings(bindings) push_scope bindings.each { |name, value| bind(name, value) } yield ensure pop_scope end |
#with_builtin_registry(registry) {|environment| ... } ⇒ Object
Execute a block with an overridden builtin registry.
218 219 220 221 222 223 224 225 226 |
# File 'lib/ruby/rego/environment.rb', line 218 def with_builtin_registry(registry) original = @builtin_registry memoization.with_context do @builtin_registry = registry yield self end ensure @builtin_registry = original end |