dry-struct
dry-struct copied to clipboard
Document how sum types works with structs
refs #158
For what it's worth, this was very unexpected behavior to me:
class A < Dry::Struct
attribute :id, Types::Strict::Integer
attribute :type, Types::Strict::String
attribute :name, Types::Strict::String
end
class B < Dry::Struct
attribute :id, Types::Strict::Integer
attribute :type, Types::Strict::String
attribute :name, Types::Strict::String
attribute :primary_function, Types::Strict::String.optional
end
class WithSum < Dry::Struct
attribute :sum, A | B
end
WithSum.new(
sum: B.new(
id: 123,
type: 'foo',
name: 'bar',
primary_function: 'yo'
)
) # => #<WithSum sum=#<A id=123 type="foo" name="bar">>
I expected it to return my instance of B
, not A
.
A nice first step, would be to at least document how sum types work.
But I wonder if it's intended to work this way too.
But I wonder if it's intended to work this way too.
The problem lies in implicit coercion with to_hash
. B
gets coerced to a hash and to an A
in order. It wasn't a good design choice. You can reject extra key by making the schema strict with schema schema.strict
:
class StrictStruct < Dry::Struct
schema schema.strict
end
class A < StrictStruct
attribute :id, Types::Strict::Integer
attribute :type, Types::Strict::String
attribute :name, Types::Strict::String
end
class B < StrictStruct
attribute :id, Types::Strict::Integer
attribute :type, Types::Strict::String
attribute :name, Types::Strict::String
attribute :primary_function, Types::Strict::String.optional
end
class WithSum < Dry::Struct
attribute :sum, A | B
end
This will still coerce data internally but these attempts will fail. I think we'll remove to_hash
in 2.0.
Thank you for the context @flash-gordon 🙏.
And thanks for the suggestion. I solved our case with constrained types:
Types::Strict::String.constrained(eql: 'my_type')
Yeah, I do something similar to discriminate variants. 👍