133 lines
3.1 KiB
Ruby
133 lines
3.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Dry
|
|
module Types
|
|
# Homogeneous mapping. It describes a hash with unknown keys that match a certain type.
|
|
#
|
|
# @example
|
|
# type = Dry::Types['hash'].map(
|
|
# Dry::Types['integer'].constrained(gteq: 1, lteq: 10),
|
|
# Dry::Types['string']
|
|
# )
|
|
#
|
|
# type.(1 => 'right')
|
|
# # => {1 => 'right'}
|
|
#
|
|
# type.('1' => 'wrong')
|
|
# # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
|
|
#
|
|
# type.(11 => 'wrong')
|
|
# # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
|
|
#
|
|
# @api public
|
|
class Map < Nominal
|
|
def initialize(_primitive, key_type: Types['any'], value_type: Types['any'], meta: EMPTY_HASH)
|
|
super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
|
|
end
|
|
|
|
# @return [Type]
|
|
#
|
|
# @api public
|
|
def key_type
|
|
options[:key_type]
|
|
end
|
|
|
|
# @return [Type]
|
|
#
|
|
# @api public
|
|
def value_type
|
|
options[:value_type]
|
|
end
|
|
|
|
# @return [String]
|
|
#
|
|
# @api public
|
|
def name
|
|
'Map'
|
|
end
|
|
|
|
# @param [Hash] hash
|
|
#
|
|
# @return [Hash]
|
|
#
|
|
# @api private
|
|
def call_unsafe(hash)
|
|
try(hash) { |failure|
|
|
raise MapError, failure.error.message
|
|
}.input
|
|
end
|
|
|
|
# @param [Hash] hash
|
|
#
|
|
# @return [Hash]
|
|
#
|
|
# @api private
|
|
def call_safe(hash)
|
|
try(hash) { return yield }.input
|
|
end
|
|
|
|
# @param [Hash] hash
|
|
#
|
|
# @return [Result]
|
|
#
|
|
# @api public
|
|
def try(hash)
|
|
result = coerce(hash)
|
|
return result if result.success? || !block_given?
|
|
yield(result)
|
|
end
|
|
|
|
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
#
|
|
# @return [Array] An AST representation
|
|
#
|
|
# @api public
|
|
def to_ast(meta: true)
|
|
[:map,
|
|
[key_type.to_ast(meta: true),
|
|
value_type.to_ast(meta: true),
|
|
meta ? self.meta : EMPTY_HASH]]
|
|
end
|
|
|
|
# @return [Boolean]
|
|
#
|
|
# @api public
|
|
def constrained?
|
|
value_type.constrained?
|
|
end
|
|
|
|
private
|
|
|
|
# @api private
|
|
def coerce(input)
|
|
return failure(
|
|
input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
|
|
) unless primitive?(input)
|
|
|
|
output, failures = {}, []
|
|
|
|
input.each do |k, v|
|
|
res_k = key_type.try(k)
|
|
res_v = value_type.try(v)
|
|
|
|
if res_k.failure?
|
|
failures << res_k.error
|
|
elsif output.key?(res_k.input)
|
|
failures << CoercionError.new("duplicate coerced hash key #{res_k.input.inspect}")
|
|
elsif res_v.failure?
|
|
failures << res_v.error
|
|
else
|
|
output[res_k.input] = res_v.input
|
|
end
|
|
end
|
|
|
|
if failures.empty?
|
|
success(output)
|
|
else
|
|
failure(input, MultipleError.new(failures))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|