dry-rb / dry-schema

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

Unexpected keys present in the result when attribute is defined to be hash or array of hashes #285

Open mfly opened 4 years ago

mfly commented 4 years ago

Describe the bug I need a Contract that is valid for hashes where the :data key is either a Hash with defined structure or an Array of such Hashes:

{ data: { type: 'records' } }

OR

{ data: [{ type: 'records' }] }

Following contracts work as expected:

class Contract1 < Dry::Validation::Contract
  params do
    required(:data) do
      hash { required(:type).filled(:string) }
    end
  end
end
Contract1.new.call({ data: { type: 'records', unexpected: 1 } })

# <Dry::Validation::Result{:data=>{:type=>"records"}} errors={}>
class Contract2 < Dry::Validation::Contract
  params do
    required(:data) do
      array(:hash) { required(:type).filled(:string) }
    end
  end
end
Contract2.new.call({ data: [{ type: 'records', unexpected: 1 }] })
# <Dry::Validation::Result{:data=>[{:type=>"records"}]} errors={}>

To Reproduce

Unfortunately a Contract that combines those two rules leaks unexpected properties when :data contains a Hash:

class Contract1or2 < Dry::Validation::Contract
  params do
    required(:data) do
      hash { required(:type).filled(:string) } |
        array(:hash) { required(:type).filled(:string) }
    end
  end
end

Contract1or2.new.call({ data: { type: 'records', unexpected: 1 } })
# <Dry::Validation::Result{:data=>{:type=>"records", :unexpected=>1}} errors={}>

It works in the second scenario:

Contract1or2.new.call({ data: [{ type: 'records', unexpected: 1 }] })
# <Dry::Validation::Result{:data=>[{:type=>"records"}]} errors={}>

Validation also seems to work:

Contract1or2.new.call({ data: { unexpected: 1 } })
# <Dry::Validation::Result{:data=>{:unexpected=>1}} errors={:data=>{:type=>["is missing"]}}>

Expected behavior Result does not include unexpected keys:

Contract1or2.new.call({ data: { type: 'records', unexpected: 1 } })
# <Dry::Validation::Result{:data=>{:type=>"records"}} errors={}>

Your environment

mfly commented 4 years ago

Closed because it's related to dry-validation - https://github.com/dry-rb/dry-validation/issues/643

mfly commented 4 years ago

Reopening, because it can be reproduced with Dry::Schema

schema = Dry::Schema.Params do
  required(:data) do
    hash { required(:type).filled(:string) } |
      array(:hash) { required(:type).filled(:string) }
  end
end

actual:

schema.call(data: { type: 'tests', undefined: 1 })
# <Dry::Schema::Result{:data=>{:type=>"tests", :undefined=>1}} errors={}>

expected:

schema.call(data: { type: 'tests', undefined: 1 })
# <Dry::Schema::Result{:data=>{:type=>"tests"}} errors={}>
solnic commented 4 years ago

Thanks for reporting this. I'm scheduling the fix for 2.0.0 because it's going to be simpler to do once a couple other improvements are done.