module Dry::Initializer # A simple structure describes an argument (either param, or option) # # @api private # class Argument include Errors UNDEFINED = Object.new.freeze # @!attribute [r] option # @return [Boolean] # Whether this is an option, or param of the initializer attr_reader :option # @!attribute [r] name # @return [Symbol] the name of the argument attr_reader :name # @!attribute [r] default # @return [Boolean] whether the argument has a default value attr_reader :default # @!attribute [r] default_value # @return [Object] the default value of the argument attr_reader :default_value # @!attribute [r] type # @return [Class, nil] a type constraint attr_reader :type # @!attribute [r] reader # @return [Boolean] whether an attribute reader is defined for the argument attr_reader :reader def initialize(name, option:, reader: true, **options) @name = name.to_sym @option = option @reader = reader assign_default_value(options) assign_type(options) end def ==(other) other.name == name end def signature case [option, default] when [false, false] then name when [false, true] then "#{name} = Dry::Initializer::Argument::UNDEFINED" when [true, false] then "#{name}:" else "#{name}: Dry::Initializer::Argument::UNDEFINED" end end def assignment "@#{name} = #{name}" end def default_assignment "@#{name} = instance_eval(&__arguments__[:#{name}].default_value)" \ " if #{name} == Dry::Initializer::Argument::UNDEFINED" end def type_constraint "__arguments__[:#{name}].type.call(@#{name})" end private def assign_default_value(options) @default = options.key? :default return unless @default @default_value = options[:default] return if Proc === @default_value fail InvalidDefaultValueError.new(@default_value) end def assign_type(options) return unless options.key? :type type = options[:type] type = plain_type_to_proc(type) if Module === type fail InvalidTypeError.new(type) unless type.respond_to? :call @type = type end def plain_type_to_proc(type) proc { |value| fail TypeError.new(type, value) unless type === value } end end end