1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
# 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)
unless primitive?(input)
return failure(
input, CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
)
end
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
|