dry-rb / dry-types

Flexible type system for Ruby with coercions and constraints
https://dry-rb.org/gems/dry-types
MIT License
861 stars 134 forks source link

Using transform_keys breaks error handling #448

Closed webdestroya closed 2 years ago

webdestroya commented 2 years ago

Describe the bug

If a model is given an object where the transform_keys block has to change the key, then the error handling is broken. It gives an error about the error.

To Reproduce

require "dry-types"
require "dry-struct"
require 'json'

module Types
  include Dry.Types(default: :strict)
  NBoolean = Bool.optional.default(nil)
end

class BaseModel < Dry::Struct
  transform_keys(&:to_sym)
end

class OtherThing < BaseModel
  attribute :somebool, Types::NBoolean.default(true.freeze)
end

class Thing < BaseModel
  attribute :services, Types::Hash.map(Types::String, OtherThing)
end

data = {
  services: {
    foo: {
      somebool: "not a boolean"
    }
  }
}

# stringify the keys
data = JSON.parse(JSON.generate(data))

puts Thing.new(data).inspect

Results in the following exception:

/gems/dry-types-1.5.1/lib/dry/types/errors.rb:59:in `map': undefined method `message' for "[OtherThing.new] \\"not a boolean\\" (String) has invalid type for :somebool violates constraints (type?(FalseClass, \\"not a boolean\\") failed)":String (NoMethodError)

      errors.map(&:message).join(", ")
            ^^^^
from /gems/dry-types-1.5.1/lib/dry/types/errors.rb:59:in `message'
from /gems/dry-types-1.5.1/lib/dry/types/map.rb:56:in `block in call_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/map.rb:78:in `try'
from /gems/dry-types-1.5.1/lib/dry/types/map.rb:55:in `call_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/schema/key.rb:49:in `call_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/schema.rb:328:in `block in resolve_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `each'
from /gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `resolve_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/schema.rb:62:in `call_unsafe'
from /gems/dry-types-1.5.1/lib/dry/types/constructor.rb:87:in `call_unsafe'
from /gems/dry-struct-1.4.0/lib/dry/struct/class_interface.rb:265:in `new'
from spec/dry_error_pr.rb:34:in `<main>'

Expected behavior

I would expect to get the proper error chain for my bogus boolean:

(When I comment out the JSON line at the end, and run it I get:

/gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:332:in `rescue in block in resolve_unsafe': [Thing.new] {:foo=>{:somebool=>"not a boolean"}} (Hash) has invalid type for :services violates constraints (:foo violates constraints (type?(String, :foo) failed) failed) (Dry::Struct::Error)
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:327:in `block in resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `each'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:62:in `call_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/constructor.rb:87:in `call_unsafe'
  from /gems/3.1.0/gems/dry-struct-1.4.0/lib/dry/struct/class_interface.rb:265:in `new'
  from spec/dry_error_pr.rb:31:in `<main>'
/gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:332:in `rescue in block in resolve_unsafe': {:foo=>{:somebool=>"not a boolean"}} (Hash) has invalid type for :services violates constraints (:foo violates constraints (type?(String, :foo) failed) failed) (Dry::Types::SchemaError)
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:327:in `block in resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `each'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:62:in `call_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/constructor.rb:87:in `call_unsafe'
  from /gems/3.1.0/gems/dry-struct-1.4.0/lib/dry/struct/class_interface.rb:265:in `new'
  from spec/dry_error_pr.rb:31:in `<main>'
/gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/map.rb:56:in `block in call_unsafe': :foo violates constraints (type?(String, :foo) failed) (Dry::Types::MapError)
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/map.rb:78:in `try'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/map.rb:55:in `call_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema/key.rb:49:in `call_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:328:in `block in resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `each'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:322:in `resolve_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/schema.rb:62:in `call_unsafe'
  from /gems/3.1.0/gems/dry-types-1.5.1/lib/dry/types/constructor.rb:87:in `call_unsafe'
  from /gems/3.1.0/gems/dry-struct-1.4.0/lib/dry/struct/class_interface.rb:265:in `new'
  from spec/dry_error_pr.rb:31:in `<main>'

My environment