lib/contracts/contract/validators.rb
module Contracts
module Validators
DEFAULT_VALIDATOR_STRATEGIES = {
# e.g. lambda {true}
Proc => lambda { |contract| contract },
# e.g. [Num, String]
# TODO: account for these errors too
Array => lambda do |contract|
lambda do |arg|
return false unless arg.is_a?(Array) && arg.length == contract.length
arg.zip(contract).all? do |_arg, _contract|
Contract.valid?(_arg, _contract)
end
end
end,
# e.g. { :a => Num, :b => String }
Hash => lambda do |contract|
lambda do |arg|
return false unless arg.is_a?(Hash)
contract.keys.all? do |k|
Contract.valid?(arg[k], contract[k])
end
end
end,
Range => lambda do |contract|
lambda do |arg|
contract.include?(arg)
end
end,
Regexp => lambda do |contract|
lambda do |arg|
arg =~ contract
end
end,
Contracts::SplatArgs => lambda do |contract|
lambda do |arg|
Contract.valid?(arg, contract.contract)
end
end,
Contracts::Func => lambda do |_|
lambda do |arg|
arg.is_a?(Method) || arg.is_a?(Proc)
end
end,
:valid => lambda do |contract|
lambda { |arg| contract.valid?(arg) }
end,
:class => lambda do |contract|
lambda { |arg| arg.is_a?(contract) }
end,
:default => lambda do |contract|
lambda { |arg| contract == arg }
end
}.freeze
# Allows to override validator with custom one.
# Example:
# Contract.override_validator(Array) do |contract|
# lambda do |arg|
# # .. implementation for Array contract ..
# end
# end
#
# Contract.override_validator(:class) do |contract|
# lambda do |arg|
# arg.is_a?(contract) || arg.is_a?(RSpec::Mocks::Double)
# end
# end
def override_validator(name, &block)
validator_strategies[name] = block
end
# This is a little weird. For each contract
# we pre-make a proc to validate it so we
# don't have to go through this decision tree every time.
# Seems silly but it saves us a bunch of time (4.3sec vs 5.2sec)
def make_validator!(contract)
validator_strategies[validator_key(contract)].call(contract)
end
def validator_key(contract)
klass = contract.class
return klass if validator_strategies.key?(klass)
return :valid if contract.respond_to? :valid?
return :class if klass == Class || klass == Module
:default
end
def make_validator(contract)
contract_id = Support.contract_id(contract)
if memoized_validators.key?(contract_id)
return memoized_validators[contract_id]
end
memoized_validators[contract_id] = make_validator!(contract)
end
# @private
def reset_validators
clean_memoized_validators
restore_validators
end
# @private
def validator_strategies
@_validator_strategies ||= restore_validators
end
# @private
def restore_validators
@_validator_strategies = DEFAULT_VALIDATOR_STRATEGIES.dup
end
# @private
def memoized_validators
@_memoized_validators ||= clean_memoized_validators
end
# @private
def clean_memoized_validators
@_memoized_validators = {}
end
end
end