Class: Ruby::Rego::Environment

Inherits:
Object
  • Object
show all
Includes:
EnvironmentOverrides, EnvironmentReferenceResolution
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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EnvironmentReferenceResolution

#reference_key_for, #resolve_reference

Methods included from EnvironmentOverrides

#with_overrides

Constructor Details

#initialize(input: {}, data: {}, rules: {}, builtin_registry: Builtins::BuiltinRegistry.instance) ⇒ Environment

Create an evaluation environment.

:reek:TooManyStatements

Parameters:

  • input (Object) (defaults to: {})

    input document

  • data (Object) (defaults to: {})

    data document

  • rules (Hash) (defaults to: {})

    rule index

  • builtin_registry (Builtins::BuiltinRegistry, Builtins::BuiltinRegistryOverlay) (defaults to: Builtins::BuiltinRegistry.instance)

    registry



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_registryBuiltins::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

#dataValue (readonly)

Data document as a Rego value.

Returns:



35
36
37
# File 'lib/ruby/rego/environment.rb', line 35

def data
  @data
end

#inputValue (readonly)

Input document as a Rego value.

Returns:



30
31
32
# File 'lib/ruby/rego/environment.rb', line 30

def input
  @input
end

#memoizationMemoization::Store (readonly)

Memoization store for evaluation caches.

Returns:



50
51
52
# File 'lib/ruby/rego/environment.rb', line 50

def memoization
  @memoization
end

#rulesHash (readonly)

Rule index by name.

Returns:

  • (Hash)


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.

Parameters:

Returns:



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.

Parameters:

  • name (String, Symbol)

    binding name

  • value (Object)

    value to bind

Returns:

  • (Value)

    bound value

Raises:



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.

Returns:

  • (bool)


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.

Parameters:

  • keys (Array<String>, nil)

Returns:

  • (bool)


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.

Parameters:

  • name (String, Symbol)

    binding name

Returns:

  • (Boolean)


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

Parameters:

  • name (String, Symbol)

    binding name

Returns:

  • (Value)

    resolved value or undefined



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_scopevoid

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_poolEnvironment

Reset the environment for pool reuse.

Returns:



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_scopeEnvironment

Push a new scope for local bindings.

Returns:



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.

Parameters:

  • state (State)

    reset state

Returns:



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.

Parameters:

  • state (State)

    reset state

Returns:



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.

Parameters:

Returns:

  • (Object)

    block result



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.

Parameters:

  • bindings (Hash{String, Symbol => Object})

    bindings to apply

Yield Returns:

  • (Object)

Returns:

  • (Object)

    block result



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.

Parameters:

Yield Parameters:

Returns:

  • (Object)

    block result



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