dry-rb / dry-schema

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

Unexpected error when validate_keys is set to true, gets "is not allowed" to ofen #450

Closed stoivo closed 1 year ago

stoivo commented 1 year ago

Describe the bug

When validate_keys is set to true for a schema it return the an error like "is not allowed" even when it is required.

To Reproduce

require "bundler/inline"

gemfile do
  source "https://rubygems.org"

  gem "dry-validation", "~> 1.10"
  gem "dry-types", "~> 1.7"
end

require "dry-types"
require "dry/validation"

module Types
  include Dry.Types
end

DemoSchema = Dry::Schema.define do
  config.validate_keys = true

  required(:addresses).each do
    hash do
      optional(:street_name).value(Types::String)
      required(:number).value(Types::String)
    end
  end
end

DemoSchema.call({addresses: [{}]})
  .tap { pp(_1)}
  .tap { pp(_1.errors.to_h)}
# =>
# #<Dry::Schema::Result{:addresses=>[{}]} errors={:addresses=>{0=>{:number=>["is missing"]}}} path=[]>
# {:addresses=>{0=>{:number=>["is missing"]}}}

DemoSchema.call({addresses: [{number: "oo"}]})
  .tap { pp(_1)}
  .tap { pp(_1.errors.to_h)}
# =>
# #<Dry::Schema::Result{:addresses=>[{:number=>"oo"}]} errors={:addresses=>{0=>{:number=>["is not allowed"]}}} path=[]>
# {:addresses=>{0=>{:number=>["is not allowed"]}}}

Expected behavior

I expect it to work for this hash {addresses: [{number: "oo"}]}

My environment

flash-gordon commented 1 year ago

You didn't specify a type spec, key validator couldn't guess it's an array. Using array with a block works as value(:array).each do so it sets a type check and a member schema:

DemoSchema = Dry::Schema.define do
  config.validate_keys = true

  required(:addresses).array(:hash) do
    optional(:street_name).value(Types::String)
    required(:number).value(Types::String)
  end
end

dry-schema> DemoSchema.call({addresses: [{number: "oo"}]})
=> #<Dry::Schema::Result{:addresses=>[{:number=>"oo"}]} errors={} path=[]>
stoivo commented 1 year ago

Yes, that works. Thanks. Looking at dry-schema docs is most examples are with array(:hash) do ... end. I am not sure where I found the .each do hash do ... end end from.

I am not sure what a member schema is. Why is this allowed if it created a half complete schma?

flash-gordon commented 1 year ago

It used to be syntax from pre-1.0 times. Going to 2.0 type specs will be mandatory so this kind of issue should happen less often. "member schema" is a schema for array values. You can check a key is an array:

required(:whatever).value(:array)

and decide not to validate what's inside. It's a legit case, who knows what you plan to do next? However, it doesn't play nice with key validation it seems:

DemoSchema = Dry::Schema.define do
  config.validate_keys = true

  required(:addresses).array(:hash)
end

DemoSchema.call({addresses: [{number: "oo"}]}) 
=> #<Dry::Schema::Result{:addresses=>[{:number=>"oo"}]} errors={:addresses=>{0=>{:number=>["is not allowed"]}}} path=[]>

I'll post a separate issue about it.