lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb



# frozen_string_literal: true

class ReeMapper::Mapper
  contract(ArrayOf[ReeMapper::MapperStrategy], Nilor[ReeMapper::AbstractType] => Any)
  def self.build(strategies, type = nil)
    if type
      strategies.each do |strategy|
        method = strategy.method
        next if type.respond_to?(method)
        raise ReeMapper::UnsupportedTypeError, "type #{type.name} should implement method `#{method}`"
      end
    end

    klass = Class.new(self)

    klass.instance_eval do
      strategies.each do |strategy|
        method = strategy.method

        if type
          class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
            def #{method}(obj, role: nil)
              @type.#{method}(obj, role: role)
            end
          RUBY
        else
          class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
            def #{method}(obj, role: nil)
              @fields.each_with_object(@#{method}_strategy.build_object) do |(_, field), acc|
                next unless field.has_role?(role)

                if @#{method}_strategy.has_value?(obj, field)
                  value = @#{method}_strategy.get_value(obj, field)
                  unless value.nil? && field.null
                    value = field.type.public_send(:#{method}, value, role: role)
                  end
                  @#{method}_strategy.assign_value(acc, field, value)
                elsif field.optional || @#{method}_strategy.always_optional
                  if field.has_default?
                    value = field.default
                    unless value.nil? && field.null
                      value = field.type.public_send(:#{method}, value, role: role)
                    end
                    @#{method}_strategy.assign_value(acc, field, value)
                  end
                else
                  raise ReeMapper::TypeError, "Missing required field `\#{field.from}`"
                end
              end
            end
          RUBY
        end
      end
    end

    klass.new(strategies, type)
  end

  attr_reader :strategies, :strategy_methods, :fields, :type

  def initialize(strategies, type)
    @fields = {}
    @type = type
    @strategies = strategies
    @strategy_methods = strategies.map(&:method)

    strategies.each do |strategy|
      method = strategy.method
      instance_variable_set(:"@#{method}_strategy", strategy)
    end
  end

  contract(Any, Symbol, Ksplat[RestKeys => Any] => nil)
  def add_field(type, name, **opts)
    @fields[name] = ReeMapper::Field.new(type, name, **opts)
    nil
  end

  contract(None => Nilor[Symbol])
  def name
    @name
  end

  contract(Symbol => Symbol)
  def name=(name)
    @name = name
  end
end