# frozen_string_literal: true class ReeMapper::MapperFactory class << self attr_reader :types, :strategies, :wrappers end contract(Symbol => Nilor[ReeMapper::MapperStrategy]) def self.find_strategy(strategy_method) strategies.detect { _1.method == strategy_method } end contract(Symbol, ReeMapper::AbstractType, Kwargs[strategies: ArrayOf[ReeMapper::MapperStrategy]] => Class) def self.register_type(name, object_type, strategies: self.strategies) register_mapper( name, ReeMapper::Mapper.build(strategies, object_type) ) end contract(Symbol, ReeMapper::Mapper => SubclassOf[self]).throws(ArgumentError) def self.register_mapper(name, type) raise ArgumentError, "mapper registration name should not end with `?`" if name.to_s.end_with?('?') defined_strategy_method = types[name]&.flat_map(&:strategy_methods)&.detect { type.find_strategy(_1) } raise ArgumentError, "type :#{name} with `#{defined_strategy_method}` strategy already registered" if defined_strategy_method raise ArgumentError, "method :#{name} already defined" if !types.key?(name) && method_defined?(name) type = type.dup type.name = name type.freeze types[name] ||= [] types[name] << type class_eval(<<~RUBY, __FILE__, __LINE__ + 1) def #{name}(field_name = nil, optional: false, **opts) raise ReeMapper::Error, "invalid DSL usage" unless @mapper raise ArgumentError, "wrapped item can't be optional" if field_name.nil? && optional type = self.class.types.fetch(:#{name}).detect { (@mapper.strategy_methods - _1.strategy_methods).empty? } unless type raise ReeMapper::UnsupportedTypeError, "type :#{name} should implement `\#{@mapper.strategy_methods.join(', ')}`" end field = ReeMapper::Field.new( type, field_name, optional: optional, **opts, location: caller_locations&.first&.to_s ) return field unless field_name @mapper.add_field(field) end def #{name}?(field_name, **opts) #{name}(field_name, optional: true, **opts) end RUBY self end contract(Symbol, SubclassOf[ReeMapper::AbstractWrapper] => SubclassOf[self]) def self.register_wrapper(name, wrapper) raise ArgumentError, "wrapper registration name should not end with `?`" if name.to_s.end_with?('?') raise ArgumentError, "method :#{name} already defined" if !wrappers.key?(name) && method_defined?(name) wrappers[name] ||= [] wrappers[name] << wrapper class_eval(<<~RUBY, __FILE__, __LINE__ + 1) contract( Nilor[Symbol, ReeMapper::Field], Nilor[ReeMapper::Field], Kwargs[optional: Bool, dto: Nilor[Class]], Ksplat[RestKeys => Any], Optblock => Nilor[ReeMapper::Field] ).throws(ReeMapper::Error, ArgumentError, ReeMapper::UnsupportedTypeError) def #{name}(field_name = nil, subject = nil, optional: false, dto: nil, **opts, &blk) raise ReeMapper::Error, "invalid DSL usage" unless @mapper raise ArgumentError, 'wrapped type does not permit :dto without :block' if dto && !blk if field_name.is_a?(ReeMapper::Field) raise ArgumentError, "field_name should be a Symbol" if subject subject = field_name field_name = nil end raise ArgumentError, "wrapped item can't be optional" if field_name.nil? && optional raise ArgumentError, "wrapped type should use either :subject or :block" if subject && blk || !subject && !blk if blk subject = ReeMapper::Field.new( hash_from_blk(dto: dto, &blk), location: caller_locations&.first&.to_s, ) end wrapper = self.class.wrappers.fetch(:#{name}).detect do |wrapper| @mapper.strategy_methods.all? { wrapper.method_defined?(_1) } end unless wrapper raise ReeMapper::UnsupportedTypeError, "wrapper :#{name} should implement `\#{@mapper.strategy_methods.join(', ')}`" end type = ReeMapper::Mapper.build(@mapper.strategies, wrapper.new(subject)) type.name = :#{name} field = ReeMapper::Field.new( type, field_name, optional: optional, **opts, location: caller_locations&.first&.to_s, ) return field unless field_name @mapper.add_field(field) end def #{name}?(*args, **opts, &blk) #{name}(*args, optional: true, **opts, &blk) end RUBY self end contract( Kwargs[ register_as: Nilor[Symbol] ], Optblock => ReeMapper::MapperFactoryProxy).throws(ArgumentError ) def self.call(register_as: nil, &blk) ReeMapper::MapperFactoryProxy.new(self, register_as: register_as, &blk) end contract(ReeMapper::Mapper => Any) def initialize(mapper) @mapper = mapper end contract(Symbol, Kwargs[dto: Nilor[Class]], Ksplat[RestKeys => Any], Block => nil) def hash(field_name, dto: nil, **opts, &blk) raise ReeMapper::Error, "invalid DSL usage" unless @mapper type = hash_from_blk(dto: dto, &blk) field = ReeMapper::Field.new(type, field_name, **opts, location: caller_locations&.first&.to_s) @mapper.add_field(field) end contract(Symbol, Ksplat[RestKeys => Any], Block => nil) def hash?(field_name, **opts, &blk) hash(field_name, optional: true, **opts, &blk) end private def hash_from_blk(dto:, &blk) mapper_proxy = self.class.call strategies = @mapper.strategies.map do |strategy| strategy = strategy.dup strategy.dto = dto if dto strategy end strategies[0..-2].each do |strategy| mapper_proxy.use(strategy) end _hsh_mapper = mapper_proxy.use(strategies.last, &blk) end end