gcanti / io-ts

Runtime type system for IO decoding/encoding
https://gcanti.github.io/io-ts/
MIT License
6.71k stars 329 forks source link

Generic Types with constraint #683

Closed shawnshaddock closed 1 year ago

shawnshaddock commented 1 year ago

I'm trying to make codecs for the following types

type Entity = {
  id: string;
};

type AddEntityRequest<E extends Entity> = {
  entity: E;
};

type User = Entity & {
  name: string;
};

type AddUserRequest = AddEntityRequest<User>;

So I tried this (based on the guidelines in the documentation https://github.com/gcanti/io-ts/blob/master/index.md#generic-types)

import * as t from 'io-ts';
import * as tt from 'io-ts-types';

const Entity = t.type({
  id: t.string,
});

type Entity = t.TypeOf<typeof Entity>;

const addEntityRequest = <E extends t.Type<Entity>>(entity: E) =>
  t.type({
    entity,
  });

const User = t.intersection([
  Entity,
  t.type({
    name: t.string,
  }),
]);

type User = t.TypeOf<typeof User>;

const AddUserRequest = addEntityRequest(User);

type AddUserRequest = t.TypeOf<typeof AddUserRequest>;

but this line is giving me an error where I pass the User codec into addEntityRequest

const AddUserRequest = addEntityRequest(User);
Argument of type 'IntersectionC<[TypeC<{ id: StringC; }>, TypeC<{ name: StringC; }>]>' is not assignable to parameter of type 'Type<{ id: string; }, { id: string; }, unknown>'.
  Types of property 'encode' are incompatible.
    Type 'Encode<{ id: string; } & { name: string; }, { id: string; } & { name: string; }>' is not assignable to type 'Encode<{ id: string; }, { id: string; }>'.
      Type '{ id: string; }' is not assignable to type '{ id: string; } & { name: string; }'.
        Property 'name' is missing in type '{ id: string; }' but required in type '{ name: string; }'.ts(2345)

I can only get it to work if I change the generic constraint on addEntityRequest to t.Mixed

const addEntityRequest = <E extends t.Mixed>(entity: E) =>
  t.type({
    entity,
  });

But then this won't enforce the passed in codec be an Entity codec. I think I must be missing something when defining the generic constraint, but can't seem to figure it out. Any help would be greatly appreciated, thanks!

ankrut commented 1 year ago

Use E extends Entity instead of E extends t.type<Entity>. You want to extend Entity, not t.type<Entity>.

const addEntityRequest = <E extends Entity>(entity: t.Type<E>) =>
  t.type({
    entity,
  });