dry-rb / dry-schema

Coercion and validation for data structures
https://dry-rb.org/gems/dry-schema
MIT License
425 stars 109 forks source link

Types.Interface only validates the last method in the list. #375

Open swilgosz opened 3 years ago

swilgosz commented 3 years ago

Describe the bug

Types.Interface only returns an error, if the second method in the list is missing, while not in case of first method missing.

Let's say, I have an interface with :foo and :bar. Then In case of having two classes, one implementing foo, and other implementing bar only, then in both scenarios dry-schema should return errors, as dry-types rises errors in both cases.

To Reproduce

# frozen_string_literal: true

require 'faker'

class Adapter1
  def call
    10.times.map { Faker::Name.name }
  end
end

class Adapter2
  def check_connection
    true
  end
end

class Adapter3
  def call
    10.times.map { Faker::Name.name }
  end

  def check_connection
    true
  end
end

# Defines Interface type requireing 2 methods to be implemented.
#
require 'dry/types'
module Types
  include Dry::Types()
end
Adapter = Types.Interface(:call, :check_connection)

# Checks if dry-types behaves as expected

# Should fail, only call check_connection method implemented
begin
  Adapter.call(Adapter1.new)
rescue StandardError
  pp "Adapter1 failed"
end

# Should fail, only check_connection method implemented
begin
  Adapter.call(Adapter2.new)
rescue StandardError
  pp "Adapter2 failed"
end

# This should not fail, both methods implemented
begin
  Adapter.call(Adapter3.new)
rescue StandardError
  pp "Adapter3 failed"
end

require 'dry/schema'

class Importer
  def call
  end

  private

  attr_reader :providers, :schema

  def initialize(*args)
    schema = Dry::Schema.Params do
      required(:providers).filled(:array?).each(Adapter)
    end

    pp '-----'
    pp schema.call(*args)
  end
end

pp Importer.new(
  providers: [Adapter1.new, Adapter2.new, Adapter3.new]
).inspect

# => #<Dry::Schema::Result{
  # :providers=>[#<Adapter1:0x00007fdf0b162aa0>, #<Adapter2:0x00007fdf0b162a78>, #<Adapter3:0x00007fdf0b162a50>]}
  # errors={:providers=>{0=>["must respond to check_connection"]}} path=[]>

# As you can see, only first adapter is reported as invalid, while the second should also be included here

Expected behavior

From the script above, I expect Adapter1 AND Adapter2 to be returned with errors.

My environment