# 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