Different errror on union with and without dispatch
Version
3.2.23
Expected behaviour
Errors should be same or at least close
Actual behaviour
This is follow up of https://github.com/gcanti/tcomb/issues/295#issuecomment-329029432.
After using of union of interfaces instead of union of structs with dispatch, turned out that type errors are now different and at some point are less informative.
Steps to reproduce
Taken this code:
const Price = t.union([t.Number, t.interface({
from: t.Boolean,
value: t.Number
}, { name: 'PriceObject', strict: true })])
const BaseOffer = t.interface({
id: t.String,
price: Price
}, { name: 'Offer', strict: true })
const SimplifiedOffer = BaseOffer.extend({
name: t.String
}, { name: 'SimplifiedOffer', strict: true })
const FreeOffer = BaseOffer.extend({
type: t.String,
model: t.String
}, { name: 'FreeOffer', strict: true })
const Offer = t.union([FreeOffer, SimplifiedOffer], 'Offer')
Offer.dispatch = (x) => x.type
? FreeOffer
: SimplifiedOffer
Offer({ id: 'nope', price: { value: 12 } })
will throw:
[tcomb] Invalid value {
"value": 12
} supplied to Offer(SimplifiedOffer)/price: Number | PriceObject (no constructor returned by dispatch)"
but if price would be changed to
const PriceObject = t.interface({
from: t.Boolean,
value: t.Number
}, { name: 'PriceObject', strict: true })
const Price = t.union([t.Number, PriceObject])
Price.dispatch = (x) => t.Number.is(x)
? t.Number
: PriceObject
it will throw
[tcomb] Invalid value undefined supplied to Offer(SimplifiedOffer)/price: Number | PriceObject(PriceObject)/from: Boolean
Notice here /from part, which indicates clearly which exactly property lacks the value, while in first case it is unclear
Now, relatively same thing happens with other interfaces or structs without dispatch. Here is another example (close to first, but slightly simplified):
const BaseOffer = t.interface({
id: t.String
}, { name: 'Offer', strict: true })
const SimplifiedOffer = BaseOffer.extend({
name: t.String
}, { name: 'SimplifiedOffer', strict: true })
const FreeOffer = BaseOffer.extend({
type: t.String,
model: t.String
}, { name: 'FreeOffer', strict: true })
const Offer = t.union([FreeOffer, SimplifiedOffer], 'Offer')
Offer.dispatch = (x) => x.type
? FreeOffer
: SimplifiedOffer
Given
Offer({ id: 'nope' }))
It will throw
[tcomb] Invalid value undefined supplied to Offer(SimplifiedOffer)/name: String
But if dispatch from Offer removed (since interfaces do not require them):
const Offer = t.union([FreeOffer, SimplifiedOffer], 'Offer')
Error message suddenly becomes much more obscure:
[tcomb] Invalid value {
"id": "nope"
} supplied to Offer (no constructor returned by dispatch)
Note, that it is no longer clear to which interface resolved union, and it no longer clearly says that name property has no value, while should
This is due to how the default dispatch is defined: it may return undefined while your custom dispatch always returns a constructor
EDIT: also note that defining a custom dispatch may be a good strategy even when using interfaces since can be smarter than the default one: in your first use case you have just two constructors and the first is t.Number