lib/dry/types/hash.rb



require 'dry/types/hash/schema_builder'

module Dry
  module Types
    class Hash < Definition
      SCHEMA_BUILDER = SchemaBuilder.new.freeze

      # @param [{Symbol => Definition}] type_map
      # @param [Symbol] constructor
      # @return [Schema]
      def schema(type_map, constructor = nil)
        member_types = transform_types(type_map)

        if constructor.nil?
          Schema.new(primitive, member_types: member_types, **options, meta: meta)
        else
          SCHEMA_BUILDER.(
            primitive,
            **options,
            member_types: member_types,
            meta: meta,
            hash_type: constructor
          )
        end
      end

      # Build a map type
      #
      # @param [Type] key_type
      # @param [Type] value_type
      # @return [Map]
      def map(key_type, value_type)
        Map.new(
          primitive,
          key_type: resolve_type(key_type),
          value_type: resolve_type(value_type),
          meta: meta
        )
      end

      # @param [{Symbol => Definition}] type_map
      # @return [Schema]
      def weak(type_map)
        schema(type_map, :weak)
      end

      # @param [{Symbol => Definition}] type_map
      # @return [Schema]
      def permissive(type_map)
        schema(type_map, :permissive)
      end

      # @param [{Symbol => Definition}] type_map
      # @return [Schema]
      def strict(type_map)
        schema(type_map, :strict)
      end

      # @param [{Symbol => Definition}] type_map
      # @return [Schema]
      def strict_with_defaults(type_map)
        schema(type_map, :strict_with_defaults)
      end

      # @param [{Symbol => Definition}] type_map
      # @return [Schema]
      def symbolized(type_map)
        schema(type_map, :symbolized)
      end

      # Build a schema from an AST
      # @api private
      # @param [{Symbol => Definition}] member_types
      # @return [Schema]
      def instantiate(member_types)
        SCHEMA_BUILDER.instantiate(primitive, **options, member_types: member_types)
      end

      # Injects a type transformation function for building schemas
      # @param [#call,nil] proc
      # @param [#call,nil] block
      # @return [Hash]
      def with_type_transform(proc = nil, &block)
        fn = proc || block

        if fn.nil?
          raise ArgumentError, "a block or callable argument is required"
        end

        handle = Dry::Types::FnContainer.register(fn)
        meta(type_transform_fn: handle)
      end

      private

      # @api private
      def transform_types(type_map)
        type_fn = meta.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
        type_transform = Dry::Types::FnContainer[type_fn]

        type_map.each_with_object({}) { |(name, type), result|
          result[name] = type_transform.(
            resolve_type(type),
            name
          )
        }
      end

      # @api private
      def resolve_type(type)
        case type
        when String, Class then Types[type]
        else type
        end
      end
    end
  end
end