145 lines
3.7 KiB
Ruby
145 lines
3.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'dry/types/hash/constructor'
|
|
|
|
module Dry
|
|
module Types
|
|
# Hash types can be used to define maps and schemas
|
|
#
|
|
# @api public
|
|
class Hash < Nominal
|
|
NOT_REQUIRED = { required: false }.freeze
|
|
|
|
# @overload schema(type_map, meta = EMPTY_HASH)
|
|
# @param [{Symbol => Dry::Types::Nominal}] type_map
|
|
# @param [Hash] meta
|
|
# @return [Dry::Types::Schema]
|
|
#
|
|
# @overload schema(keys)
|
|
# @param [Array<Dry::Types::Schema::Key>] key List of schema keys
|
|
# @param [Hash] meta
|
|
# @return [Dry::Types::Schema]
|
|
#
|
|
# @api public
|
|
def schema(keys_or_map, meta = EMPTY_HASH)
|
|
if keys_or_map.is_a?(::Array)
|
|
keys = keys_or_map
|
|
else
|
|
keys = build_keys(keys_or_map)
|
|
end
|
|
|
|
Schema.new(primitive, keys: keys, **options, meta: self.meta.merge(meta))
|
|
end
|
|
|
|
# Build a map type
|
|
#
|
|
# @param [Type] key_type
|
|
# @param [Type] value_type
|
|
#
|
|
# @return [Map]
|
|
#
|
|
# @api public
|
|
def map(key_type, value_type)
|
|
Map.new(
|
|
primitive,
|
|
key_type: resolve_type(key_type),
|
|
value_type: resolve_type(value_type),
|
|
meta: meta
|
|
)
|
|
end
|
|
|
|
# @api private
|
|
def weak(*)
|
|
raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
|
|
"on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
|
|
end
|
|
alias_method :permissive, :weak
|
|
alias_method :strict, :weak
|
|
alias_method :strict_with_defaults, :weak
|
|
alias_method :symbolized, :weak
|
|
|
|
# Injects a type transformation function for building schemas
|
|
#
|
|
# @param [#call,nil] proc
|
|
# @param [#call,nil] block
|
|
#
|
|
# @return [Hash]
|
|
#
|
|
# @api public
|
|
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)
|
|
with(type_transform_fn: handle)
|
|
end
|
|
|
|
# @api private
|
|
def constructor_type
|
|
::Dry::Types::Hash::Constructor
|
|
end
|
|
|
|
# Whether the type transforms types of schemas created by {Dry::Types::Hash#schema}
|
|
#
|
|
# @return [Boolean]
|
|
#
|
|
# @api public
|
|
def transform_types?
|
|
!options[:type_transform_fn].nil?
|
|
end
|
|
|
|
# @param meta [Boolean] Whether to dump the meta to the AST
|
|
#
|
|
# @return [Array] An AST representation
|
|
#
|
|
# @api public
|
|
def to_ast(meta: true)
|
|
if RUBY_VERSION >= "2.5"
|
|
opts = options.slice(:type_transform_fn)
|
|
else
|
|
opts = options.select { |k, _| k == :type_transform_fn }
|
|
end
|
|
|
|
[:hash, [opts, meta ? self.meta : EMPTY_HASH]]
|
|
end
|
|
|
|
private
|
|
|
|
# @api private
|
|
def build_keys(type_map)
|
|
type_fn = options.fetch(:type_transform_fn, Schema::NO_TRANSFORM)
|
|
type_transform = Dry::Types::FnContainer[type_fn]
|
|
|
|
type_map.map do |map_key, type|
|
|
name, options = key_name(map_key)
|
|
key = Schema::Key.new(resolve_type(type), name, options)
|
|
type_transform.(key)
|
|
end
|
|
end
|
|
|
|
# @api private
|
|
def resolve_type(type)
|
|
case type
|
|
when String, Class then Types[type]
|
|
else type
|
|
end
|
|
end
|
|
|
|
# @api private
|
|
def key_name(key)
|
|
if key.to_s.end_with?('?')
|
|
[key.to_s.chop.to_sym, NOT_REQUIRED]
|
|
else
|
|
[key, EMPTY_HASH]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
require 'dry/types/schema/key'
|
|
require 'dry/types/schema'
|