class Stannum::Constraints::Hashes::ExtraKeys

Constraint for validating the keys of a hash-like object.

@example

keys       = %[fuel mass size]
constraint = Stannum::Constraints::Hashes::ExpectedKeys.new(keys)

constraint.matches?({})                                #=> true
constraint.matches?({ fuel: 'Monopropellant' })        #=> true
constraint.matches?({ electric: true, fuel: 'Xenon' }) #=> false
constraint.matches?({ fuel: 'LF/O', mass: '1 ton', size: 'Medium' })
#=> true
constraint.matches?(
  { fuel: 'LF', mass: '2 tons', nuclear: true, size: 'Medium' }
)
#=> false

Constants

NEGATED_TYPE

The :type of the error generated for a matching object.

TYPE

The :type of the error generated for a non-matching object.

Public Class Methods

new(expected_keys, **options) click to toggle source

@param expected_keys [Array, Proc] The expected keys. If a Proc, will be

evaluated each time the constraint is matched.

@param options [Hash<Symbol, Object>] Configuration options for the

constraint. Defaults to an empty Hash.
Calls superclass method Stannum::Constraints::Base::new
# File lib/stannum/constraints/hashes/extra_keys.rb, line 33
def initialize(expected_keys, **options)
  validate_expected_keys(expected_keys)

  expected_keys =
    if expected_keys.is_a?(Array)
      Set.new(expected_keys)
    else
      expected_keys
    end

  super(expected_keys: expected_keys, **options)
end

Public Instance Methods

does_not_match?(actual) click to toggle source

@return [true, false] true if the object responds to [] and keys and the

object has at least one key that is not in expected_keys.
# File lib/stannum/constraints/hashes/extra_keys.rb, line 48
def does_not_match?(actual)
  return false unless hash?(actual)

  !(Set.new(actual.keys) <= expected_keys) # rubocop:disable Style/InverseMethods
end
errors_for(actual, errors: nil) click to toggle source

(see Stannum::Constraints::Base#errors_for)

# File lib/stannum/constraints/hashes/extra_keys.rb, line 55
def errors_for(actual, errors: nil)
  errors ||= Stannum::Errors.new

  unless actual.respond_to?(:keys)
    return add_invalid_hash_error(actual: actual, errors: errors)
  end

  each_extra_key(actual) do |key, value|
    key = Stannum::Support::Coercion.error_key(key)

    errors[key].add(type, value: value)
  end

  errors
end
expected_keys() click to toggle source

@return [Array] the expected keys.

# File lib/stannum/constraints/hashes/extra_keys.rb, line 72
def expected_keys
  keys = options[:expected_keys]

  return keys unless keys.is_a?(Proc)

  Set.new(keys.call)
end
match?(actual)
Alias for: matches?
matches?(actual) click to toggle source

@return [true, false] true if the object responds to [] and keys and the

object does not have any key that is not in expected_keys.
# File lib/stannum/constraints/hashes/extra_keys.rb, line 82
def matches?(actual)
  return false unless actual.respond_to?(:keys)

  Set.new(actual.keys) <= expected_keys
end
Also aliased as: match?

Private Instance Methods

add_invalid_hash_error(actual:, errors:) click to toggle source
# File lib/stannum/constraints/hashes/extra_keys.rb, line 91
def add_invalid_hash_error(actual:, errors:)
  Stannum::Constraints::Signature
    .new(:keys)
    .errors_for(actual, errors: errors)
end
each_extra_key(actual) { |key, actual| ... } click to toggle source
# File lib/stannum/constraints/hashes/extra_keys.rb, line 97
def each_extra_key(actual)
  expected = expected_keys

  actual.each_key do |key|
    next if expected.include?(key)

    yield key, actual[key]
  end
end
hash?(actual) click to toggle source
# File lib/stannum/constraints/hashes/extra_keys.rb, line 107
def hash?(actual)
  actual.respond_to?(:[]) && actual.respond_to?(:keys)
end
valid_key?(key) click to toggle source
# File lib/stannum/constraints/hashes/extra_keys.rb, line 111
def valid_key?(key)
  key.is_a?(String) || key.is_a?(Symbol)
end
validate_expected_keys(expected_keys) click to toggle source
# File lib/stannum/constraints/hashes/extra_keys.rb, line 115
def validate_expected_keys(expected_keys)
  expected_keys = expected_keys.call if expected_keys.is_a?(Proc)

  unless expected_keys.is_a?(Array)
    raise ArgumentError,
      'expected_keys must be an Array or a Proc',
      caller(1..-1)
  end

  return if expected_keys.all? { |key| valid_key?(key) }

  raise ArgumentError, 'key must be a String or Symbol', caller(1..-1)
end