Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema from struct reports errors on extra fields #468

Open
paul opened this issue Sep 7, 2023 · 1 comment
Open

Schema from struct reports errors on extra fields #468

paul opened this issue Sep 7, 2023 · 1 comment

Comments

@paul
Copy link
Contributor

paul commented Sep 7, 2023

Describe the bug

I'm getting some fields included in errors that would normally be fine, in a very specific circumstance:

  • Use some of the coercible types in a Struct
  • Generate a schema from the struct
  • Call the schema with any invalid field
  • In the errors output, the fields with coercible types are included in the output, even if those fields are valid.

Might be related to #424

To Reproduce

I've included a full example at the bottom. The short version:

MySchema = Dry::Schema.Params do
  required(:date).filled(Types::JSON::DateTime)
  required(:number).filled(Types::JSON::Decimal)
  required(:string).filled(Types::String)
end

class MyStruct < Dry::Struct
  attribute :date, Types::JSON::DateTime
  attribute :number, Types::JSON::Decimal
  attribute :string, Types::String
end

SchemaFromStruct = Dry::Schema.Params do
  required(:data).value(MyStruct)
end

If I call the schema with a bogus value for date, I get the expected errors:

MySchema.call(date: "00", number: 42, string: "Hi!")
#=> #<Dry::Schema::Result errors={:date=>["must be a date time"]}>

If I call the schema generated from the struct, though, I get an extra error on a field that is really valid:

# With an invalid value for :date
SchemaFromStruct.call(data: {date: "00", number: 42, string: "Hi!"})
#=> #<Dry::Schema::Result errors={:data=>{:date=>["must be a date time"], :number=>["must be a decimal"]}}>
# Has errors on both "date" and "number", but not "string"

# With an invalid value for :number
SchemaFromStruct.call(data: {date: "09/07/2023", number: "", string: "Hi!"}).errors
#=> {:data=>{:date=>["must be a date time"], :number=>["must be a decimal"]}}
# Has errors on both "date" and "number", but not "string"

# With an invalid value for :string
SchemaFromStruct.call(data: {date: "09/07/2023", number: 42, string: 42}).errors
#=> {:data=>{:date=>["must be a date time"], :number=>["must be a decimal"], :string=>["must be a string"]}}
# Has errors on the invalid string, but also still "date" and "number" 

I suspect, but haven't really dug in, that it is the coercions that raise an exception which gets rescued are the ones that show up in errors when they're not supposed to. Eg, to_decimal on dry/types/coercions/json.rb.

Expected behavior

I'd expect a schema generated from a struct to have the same errors as an identical schema, and not have errors on fields that are valid.

My environment

  • Affects my production application: YES, but not urgently
  • Ruby version: 3.2.2
  • OS: Linux

Full test case

require "dry-types"
require "dry-struct"
require "dry-schema"

Dry::Schema.load_extensions(:struct)

module Types
  include Dry.Types()
end

MySchema = Dry::Schema.Params do
  required(:date).filled(Types::JSON::DateTime)
  required(:number).filled(Types::JSON::Decimal)
  required(:string).filled(Types::String)
end

class MyStruct < Dry::Struct
  attribute :date, Types::JSON::DateTime
  attribute :number, Types::JSON::Decimal
  attribute :string, Types::String
end

SchemaFromStruct = Dry::Schema.Params do
  required(:data).value(MyStruct)
end

good_data = {date: "09/07/2023", number: 42, string: "Hi!"}
bad_data = {date: "00", number: 42, string: "Hi!"}

puts "With Good Data"
p struct: MyStruct.new(good_data)
p schema: MySchema.call(good_data)
p schema_from_struct: SchemaFromStruct.call(data: good_data)

puts "With Bad Data"
begin
  MyStruct.new(bad_data)
rescue => ex
  p struct: ex
end
p schema: MySchema.call(bad_data)
p schema_from_struct: SchemaFromStruct.call(data: bad_data)

__END__

With Good Data
{:struct=>#<MyStruct date=#<DateTime: 2023-07-09T00:00:00+00:00 ((2460135j,0s,0n),+0s,2299161j)> number=0.42e2 string="Hi!">}
{:schema=>#<Dry::Schema::Result{:date=>#<DateTime: 2023-07-09T00:00:00+00:00 ((2460135j,0s,0n),+0s,2299161j)>, :number=>0.42e2, :string=>"Hi!"} errors={} path=[]>}
{:schema_from_struct=>#<Dry::Schema::Result{:data=>{:date=>#<DateTime: 2023-07-09T00:00:00+00:00 ((2460135j,0s,0n),+0s,2299161j)>, :number=>0.42e2, :string=>"Hi!"}} errors={} path=[]>}
With Bad Data
{:struct=>#<Dry::Struct::Error: [MyStruct.new] "00" (String) has invalid type for :date violates constraints (invalid date failed)>}
{:schema=>#<Dry::Schema::Result{:date=>"00", :number=>0.42e2, :string=>"Hi!"} errors={:date=>["must be a date time"]} path=[]>}
{:schema_from_struct=>#<Dry::Schema::Result{:data=>{:date=>"00", :number=>42, :string=>"Hi!"}} errors={:data=>{:date=>["must be a date time"], :number=>["must be a decimal"]}} path=[]>}
@flash-gordon
Copy link
Member

Thanks for filing this. At the first glance it seems it's caused by using coercions in struct definitions. I'll try having a look this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants