superstruct
superstruct copied to clipboard
union of intersections doesn't work
Here're types definitions:
import * as t from './superstruct'
const TOrderCreated = t.object({
id: t.string(),
sku: t.string(),
quantity: t.number(),
})
export const TOrderChanged = t.object({
id: t.string(),
quantity: t.number(),
})
const TOrderCanceled = t.object({
id: t.string(),
})
const TOrderEvent = t.union([
t.intersection([t.type({_type: t.literal('created')}), TOrderCreated]),
t.intersection([t.type({_type: t.literal('changed')}), TOrderChanged]),
t.intersection([t.type({_type: t.literal('canceled')}), TOrderCanceled]),
])
type OrderEvent = t.Infer<typeof TOrderEvent>
Clearly I'm using _type
as discriminator field, but I don't want to mix it with the "payload" - specific events. Hence I'm doing intersection
for adding discriminator field.
I would expect this to work properly:
let event: OrderEvent = t.create({ _type: 'changed', id: 'id123', quantity: 123 }, TOrderEvent)
However I get error:
StructError: Expected the value to satisfy a union of `intersection | intersection | intersection`, but received: [object Object]
And by the way: in io-ts
this is fully working (I'm trying to achieve with superstruct
what I already have working in io-ts
). The exact same code with only difference that Infer
is replaced with TypeOf
- I have checked this in my tests.
Actually, I have found the reason of my problems:
In the code above TOrderCreated
is t.object
(no extra fields!). So what I have is
t.intersection([t.type({......}), t.object({......})])
This is impossible because t.object
does not allow extra fields and t.intersection
is literally adding extra fields.
When I replaced t.object
with t.type
everything works as expected, so this would work:
t.intersection([t.type({_type: t.literal('created')}), t.type({......})])
Th only real issue I see here is that error message that I saw is vague - doesn't help to understand what's wrong.
@vsapronov I've been running into the same problems as you so I just released @birchill/discriminator
for this.
It adds a new struct type based on the JSON typedef discriminator type.
Usage in your example:
import * as t from './superstruct'
import { discriminator } from '@birchill/discriminator';
const TOrderCreated = t.object({
id: t.string(),
sku: t.string(),
quantity: t.number(),
})
export const TOrderChanged = t.object({
id: t.string(),
quantity: t.number(),
})
const TOrderCanceled = t.object({
id: t.string(),
})
const TOrderEvent = discriminator('_type', {
created: TOrderCreated,
changed: TOrderChanged,
canceled: TOrderCanceled,
})
type OrderEvent = t.Infer<typeof TOrderEvent>
// Produces
//
// type OrderEvent = {
// _type: 'created';
// id: string;
// sku: string;
// quantity: number;
// } | {
// _type: 'changed';
// id: string;
// quantity: number;
// } | {
// _type: 'canceled';
// id: string;
// }
When it fails to validate, it will tell you the specific field that failed, e.g.
At path: orderEvent.quantity -- Expected a number received "123"
@birtles that's awesome! If you were interested in PR'ing I'd be open to it.
@birtles that's awesome! If you were interested in PR'ing I'd be open to it.
Thanks! Sure, it will take a bit of work to get it fully ready but I'll try to get to it in the coming weeks.