-
-
Notifications
You must be signed in to change notification settings - Fork 134
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
Unexpected error raised from Sum type #439
Comments
Thanks for this detailed report, this is clearly a bug in error handling logic in |
Sum returns the last error, this was always the case. It tries the left type first, then thee right one. It also bothers me but I'm not sure this can be improved. We could probably collect all failures but this will affect performance. Also, message size can be really large for complex types. |
FWIW I don't think it's a bug. |
Oh boy, now I remember it's not the first time when I'm getting confused by this :( Maybe we should improve the error message? Like "input violates constraints of all the possible types, last error: blah blah"? |
Yeah, it would definitely make sense. |
Hi, all! First of all, just want to thank you guys for the amazing library ❤️ I've faced the same issue that a sum type raises only the error of the last type's constructor, which is really confusing. After looking into the implementation details I think the problem with returning an error that gives a more comprehensive message is that it needs to somehow pass some state from all the left types to the right ones step by step because a final sum type it's only a sum of a complex left and a simple right. Is that right? What do you think about adding a union type that is going to store all the types at the same level? Here is what I mean class Union < Dry::Types::Union
possible_types(
Dry::Types::String,
Dry::Types::Integer,
# and etc
)
end One additional benefit that we could get from adding a union type is explicit control over what type an input should be assigned. Here is an example: class A < Dry::Struct
attribute :value, Types::String
end
class B < Dry::Struct
attribute :value, Types::String
end
Data = A | B
hash = { value: 'a' }
p Data[hash].class # => It is always going to be the first left type in a sum. With a union type, it would be possible to override the default behavior, which is finding a first type whose constructor succeeds. class Data < Dry::Types::Union
possible_types A, B
class << self
def resolve(data)
case data
in { value: 'a' }
A
in { value: 'b' }
B
end
end
end
end |
Here is a draft I wrote and which worked out in my case: require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dry-types'
gem 'dry-struct'
end
module Types
include Dry.Types()
end
module Dry
module Types
class Union
include Builder
include Meta
attr_reader :types
Error = Class.new(StandardError)
def initialize(*types)
raise ArgumentError, 'a union should consist of at least 2 types' if types.size < 2
@types = types
@meta = {}
end
def try(input)
type = resolve(input)
return Dry::Types::Result::Success.new(input) if type
error = Error.new("A value does not match any type of union")
failure = Dry::Types::Result::Failure.new(input, error)
yield failure if block_given?
failure
end
def [](input)
call_unsafe(input)
end
def call_unsafe(input)
type = resolve(input)
raise Error, "value #{input} does not match any type of union" if type.nil?
type.call_unsafe(input)
end
def constrained?
false
end
private
def resolve(input)
find_type(input)
end
def find_type(input)
types.find { |type| type.valid?(input) }
end
class << self
def of(*types, &block)
new(*types).tap do |instance|
instance.instance_exec(&block) if block_given?
end
end
end
end
end
end
class A < Dry::Struct
attribute :value, Types::String
end
class B < Dry::Struct
attribute :value, Types::String
end
Data = Dry::Types::Union.of(A, B) do
def resolve(input)
case input
in { value: 'a' } if A.valid?(input)
A
in { value: 'b' } if B.valid?(input)
B
else
find_type(input)
end
end
end
p Data[{ value: 'a' }].class
p Data[{ value: 'b' }].class
p Data[{ }] # => Error I haven't really dived into all the details of what interfaces the type should implement so my point here is to just make a suggestion. I would really appreciate hearing your opinions on that @solnic @flash-gordon |
Describe the bug
👋 Hey guys, I've been playing around dry-types and found an error raised from sum types unexpected.
To Reproduce
Expected behavior
I'm expecting the error raised for
FixedAmount
, which satisfies the type checking on:type
but fails the constraint on:value
. I guess the sum type would try applying both constructors and return last failure as the error.My environment
The text was updated successfully, but these errors were encountered: