dry-types/lib/dry/types/module.rb

121 lines
3.7 KiB
Ruby

# frozen_string_literal: true
require 'dry/core/deprecations'
require 'dry/types/builder_methods'
module Dry
module Types
# Export types registered in a container as module constants.
# @example
# module Types
# include Dry::Types(:strict, :coercible, :nominal, default: :strict)
# end
#
# Types.constants
# # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
# # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
# # :Coercible, :Time]
#
# @api public
class Module < ::Module
def initialize(registry, *args)
@registry = registry
check_parameters(*args)
constants = type_constants(*args)
define_constants(constants)
extend(BuilderMethods)
if constants.key?(:Nominal)
singleton_class.send(:define_method, :included) do |base|
super(base)
base.instance_exec(const_get(:Nominal, false)) do |nominal|
extend Dry::Core::Deprecations[:'dry-types']
const_set(:Definition, nominal)
deprecate_constant(:Definition, message: "Nominal")
end
end
end
end
# @api private
def type_constants(*namespaces, default: Undefined, **aliases)
if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
default_ns = :Strict
elsif Undefined.equal?(default)
default_ns = Undefined
else
default_ns = Inflector.camelize(default).to_sym
end
tree = registry_tree
if namespaces.empty? && aliases.empty?
modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
else
modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
end
tree.each_with_object({}) do |(key, value), constants|
if modules.include?(key)
name = aliases.fetch(Inflector.underscore(key).to_sym, key)
constants[name] = value
end
constants.update(value) if key == default_ns
end
end
# @api private
def registry_tree
@registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
type = @registry[key]
*modules, const_name = key.split('.').map { |part|
Inflector.camelize(part).to_sym
}
next if modules.empty?
modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type
}.freeze
end
private
# @api private
def check_parameters(*namespaces, default: Undefined, **aliases)
referenced = namespaces.dup
referenced << default unless false.equal?(default) || Undefined.equal?(default)
referenced.concat(aliases.keys)
known = @registry.keys.map { |k|
ns, *path = k.split('.')
ns.to_sym unless path.empty?
}.compact.uniq
(referenced.uniq - known).each do |name|
raise ArgumentError,
"#{ name.inspect } is not a known type namespace. "\
"Supported options are #{ known.map(&:inspect).join(', ') }"
end
end
# @api private
def define_constants(constants, mod = self)
constants.each do |name, value|
case value
when ::Hash
if mod.const_defined?(name, false)
define_constants(value, mod.const_get(name, false))
else
m = ::Module.new
mod.const_set(name, m)
define_constants(value, m)
end
else
mod.const_set(name, value)
end
end
end
end
end
end