Skip to content
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

union of intersections doesn't work #897

Open
vsapronov opened this issue Aug 17, 2021 · 5 comments
Open

union of intersections doesn't work #897

vsapronov opened this issue Aug 17, 2021 · 5 comments

Comments

@vsapronov
Copy link

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]
@vsapronov
Copy link
Author

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.

@vsapronov
Copy link
Author

vsapronov commented Aug 23, 2021

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.

@birtles
Copy link
Contributor

birtles commented Aug 26, 2021

@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"

@ianstormtaylor
Copy link
Owner

@birtles that's awesome! If you were interested in PR'ing I'd be open to it.

@birtles
Copy link
Contributor

birtles commented Sep 9, 2021

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants