module Datacaster class HashMapper < Base def initialize(fields) keys = fields.keys.flatten if keys.length != keys.uniq.length intersection = keys.select { |k| keys.count(k) > 1 }.uniq.sort raise ArgumentError.new("When using transform_to_hash([:a, :b, :c] => validator), " \ "each key should not be mentioned more than once on the left-hand-side. Instead, got these " \ "keys mentioned twice or more: #{intersection.inspect}." ) end @fields = fields end def cast(object, runtime:) errors = {} result = {} runtime.will_check! @fields.each do |key, validator| new_value = runtime.ignore_checks! { validator.with_runtime(runtime).(object) } # transform_to_hash([:a, :b, :c] => pick(:a, :b, :c) & ...) if key.is_a?(Array) unwrapped = new_value.valid? ? new_value.value : new_value.raw_errors if key.length != unwrapped.length raise TypeError.new("When using transform_to_hash([:a, :b, :c] => validator), validator should return Array "\ "with number of elements equal to the number of elements in left-hand-side array.\n" \ "Got the following (values or errors) instead: #{keys.inspect} => #{values_or_errors.inspect}.") end end if new_value.valid? if key.is_a?(Array) key.zip(new_value.value) do |new_key, new_key_value| result[new_key] = new_key_value runtime.checked_key!(new_key) end else result[key] = new_value.value runtime.checked_key!(key) end else if key.is_a?(Array) errors = self.class.merge_errors(errors, key.zip(new_value.raw_errors).to_h) else errors = self.class.merge_errors(errors, {key => new_value.raw_errors}) end end end errors.delete_if { |_, v| v.empty? } if errors.empty? # All unchecked key-value pairs of initial hash are passed through, and eliminated by ContextNode # at the end of the chain. If we weren't dealing with the hash, then ignore that. result_hash = if object.is_a?(Hash) object.merge(result) else result end result_hash.keys.each { |k| result_hash.delete(k) if result_hash[k] == Datacaster.absent } Datacaster.ValidResult(result_hash) else Datacaster.ErrorResult(errors) end end def inspect field_descriptions = @fields.map do |k, v| "#{k.inspect} => #{v.inspect}" end "#" end end end