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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot generically create type'd union from string literals for an enum #678

Open
tsheaff opened this issue Dec 1, 2022 · 1 comment
Open

Comments

@tsheaff
Copy link

tsheaff commented Dec 1, 2022

馃悰 Bug report

Current Behavior

import * as t from 'io-ts';

const myEnumValues = [
  'value_a',
  'value_b',
  'value_c',
] as const;
export type MyEnumValue = typeof myEnumValues[number];
export const tMyEnumValue = t.union(myEnumValues.map((value) => t.literal(value)));

I would expect tMyEnumValue to be equivalent to the following:

export const tMyEnumValue = t.union([t.literal('value_a'), t.literal('value_b'), t.literal('value_c')]);

However, the explicit string literals work well while the maps do not. I get the following TS compiler error:

Argument of type 'LiteralC<"value_a" | "value_b" | "value_c">[]' is not assignable to parameter of type '[Mixed, Mixed, ...Mixed[]]'.
  Source provides no match for required element at position 0 in target.ts(2345)

Here's a screenshot in VSCode

Screenshot 2022-11-30 at 20 43 33

Expected behavior

t.union(stringsArray.map(t.literal) compiles and works properly.

Reproducible example

See above

Suggested solution(s)

I'm not a deep expert on the io-ts library, so I don't yet know how to solve this. I'm happy to learn more tho and contribute a PR if there's someone on the team willing to point me in the right direction.

Or perhaps I'm missing something here and there's an obvious way to do what I'm trying to do here.

Your environment

Which versions of io-ts are affected by this issue? Did this work in previous versions of io-ts?

Software Version(s)
io-ts io-ts@2.2.16
fp-ts fp-ts@2.12.1
TypeScript typescript@4.6.3
@mlegenhausen
Copy link
Contributor

mlegenhausen commented Dec 5, 2022

The problem is that union accepts at least two elements else creating a union makes no sense as you could for one element use the codec directly and you need to define what needs to happen when the array is empty.

In this case also the const does not make sure that you have at least two element as the map function converts your triple back to an array. You can get this working by using map only on the "rest" of your array and make sure that you have at least two elements. This looks something like this:

import * as t from 'io-ts';

const myEnumValues = [
  'value_a',
  'value_b',
  'value_c',
] as const;
export type MyEnumValue = typeof myEnumValues[number];
const [first, second, ...rest]  = myEnumValues;
export const tMyEnumValue = t.union([
  t.literal(first),
  t.literal(second), 
  ...rest.map((value) => t.literal(value))
]);

If you want to support less that two elements but at least one you could write your own union function that

import * as t from 'io-ts'
import * as NEA from 'fp-ts/NonEmptyArray'

export const relaxedUnion = <A extends NEA.NonEmptyArray<t.Mixed>>(
  codecs: A,
  name?: string,
): t.Type<t.TypeOf<A[number]>, t.OutputOf<A[number]>> =>
  pipe(
    codecs,
    NEA.matchLeft((head1, tail) =>
      pipe(
        tail,
        A.matchLeft(
          () => head1,
          (head2, tail2) => t.union([head1, head2, ...tail2], name),
        ),
      ),
    ),
  );

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

2 participants