Class: Ruby::Rego::Value

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby/rego/value.rb

Overview

Base class for Rego values. :reek:InstanceVariableAssumption (lazily memoizes @canonical)

Constant Summary collapse

TYPE_NAME =
"value"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value = nil) ⇒ Value

Create a value wrapper.

Parameters:

  • value (Object) (defaults to: nil)

    underlying value



20
21
22
# File 'lib/ruby/rego/value.rb', line 20

def initialize(value = nil)
  @value = value
end

Instance Attribute Details

#valueObject (readonly)

The wrapped Ruby value.

Returns:

  • (Object)


27
28
29
# File 'lib/ruby/rego/value.rb', line 27

def value
  @value
end

Class Method Details

.canonicalize(value) ⇒ Object

Parameters:

  • value (Object)

    a Ruby value (as produced by #to_ruby)

Returns:

  • (Object)


108
109
110
111
112
113
114
115
116
117
# File 'lib/ruby/rego/value.rb', line 108

def self.canonicalize(value)
  case value
  when Number then value.exact
  when ::Float then canonicalize_float(value)
  when ::Array then canonicalize_each(value)
  when ::Set then ::Set.new(canonicalize_each(value))
  when ::Hash then value.to_h { |key, val| [canonicalize(key), canonicalize(val)] }
  else value
  end
end

.canonicalize_each(collection) ⇒ Array

Returns:

  • (Array)


120
121
122
# File 'lib/ruby/rego/value.rb', line 120

def self.canonicalize_each(collection)
  collection.map { |element| canonicalize(element) }
end

.canonicalize_float(value) ⇒ Numeric

A Float canonicalizes to the same exact form a Number would: its shortest decimal as an exact Rational (an integer-valued Float collapsing to its Integer), so a Float produced by a not-yet- migrated builtin compares and hashes equal to a Number of the same value (1.0 == 1, 0.1 == 0.1). A non-finite Float has no exact value and is left as-is.

Returns:

  • (Numeric)


129
130
131
132
133
134
# File 'lib/ruby/rego/value.rb', line 129

def self.canonicalize_float(value)
  return value unless value.finite?

  rational = BigDecimal(value.to_s).to_r
  rational.denominator == 1 ? rational.numerator : rational
end

.from_ruby(value) ⇒ Value

Coerce Ruby values into Rego values.

Parameters:

  • value (Object)

    value to convert

Returns:

  • (Value)

    converted value

Raises:

  • (ArgumentError)


140
141
142
143
144
145
146
147
148
# File 'lib/ruby/rego/value.rb', line 140

def self.from_ruby(value)
  return value if value.is_a?(Value)
  return UndefinedValue.new if value.equal?(UndefinedValue::UNDEFINED)

  built_value = build_value(value)
  return built_value if built_value

  raise ArgumentError, "Unsupported value type: #{value.class}"
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Compare values by class and canonical Ruby value. The canonical form makes numerically-equal numbers (e.g. 1 and 1.0) compare equal everywhere — including as array/set/object members — matching OPA, where 1 == 1.0.

Parameters:

  • other (Object)

Returns:

  • (Boolean)


80
81
82
# File 'lib/ruby/rego/value.rb', line 80

def ==(other)
  other.is_a?(self.class) && other.canonical == canonical
end

#canonicalObject

The value’s canonical Ruby form: numerically-equal numbers are unified (an integer-valued Float collapses to its Integer) recursively through arrays, sets, and objects, so identity/equality/hashing match OPA’s number semantics. to_ruby still returns the original (first-seen) representation.

Returns:

  • (Object)


100
101
102
103
104
# File 'lib/ruby/rego/value.rb', line 100

def canonical
  return @canonical if defined?(@canonical)

  @canonical = self.class.canonicalize(to_ruby)
end

#fetch_reference(_key) ⇒ Value

Resolve a reference on the value.

Parameters:

  • _key (Object)

    reference key

Returns:

  • (Value)

    resolved value or undefined



54
55
56
57
58
# File 'lib/ruby/rego/value.rb', line 54

def fetch_reference(_key)
  return self if undefined?

  UndefinedValue.new
end

#hashInteger

Hash for use in Ruby collections, consistent with ==/eql? (so a Set/Hash dedups 1 and 1.0). Without this, eql?-equal numbers could hash differently and never collapse.

Returns:

  • (Integer)


90
91
92
# File 'lib/ruby/rego/value.rb', line 90

def hash
  [self.class.name, canonical].hash
end

#object_keyObject

Return a normalized object key representation.

Returns:

  • (Object)


70
71
72
# File 'lib/ruby/rego/value.rb', line 70

def object_key
  to_ruby
end

#to_rubyObject

Convert the value back to Ruby.

Returns:

  • (Object)


39
40
41
# File 'lib/ruby/rego/value.rb', line 39

def to_ruby
  value
end

#truthy?Boolean

Determine truthiness for Rego evaluation.

Returns:

  • (Boolean)


32
33
34
# File 'lib/ruby/rego/value.rb', line 32

def truthy?
  true
end

#type_nameString

Return the Rego type name.

Returns:

  • (String)


46
47
48
# File 'lib/ruby/rego/value.rb', line 46

def type_name
  self.class::TYPE_NAME
end

#undefined?Boolean

Check if the value is undefined.

Returns:

  • (Boolean)


63
64
65
# File 'lib/ruby/rego/value.rb', line 63

def undefined?
  is_a?(UndefinedValue)
end