gcanti / io-ts-codegen

Code generation for io-ts
https://gcanti.github.io/io-ts-codegen/
MIT License
155 stars 14 forks source link

Typescript reports invalid type for recursive type generated with io-ts-codegen and using UUID #42

Closed SAnDAnGE closed 5 years ago

SAnDAnGE commented 5 years ago

I use io-ts-codegen to generate my decoders (helps a lot with recursion types). I used this:

import * as gen from 'io-ts-codegen';

const declarations = [
  gen.typeDeclaration(
    'Category',
    gen.typeCombinator(
      [
        gen.property('id', gen.identifier('UUID')),
        gen.property('children', gen.arrayCombinator(gen.identifier('Category'))),
      ],
      'Category'
    ),
    true
  )
];

const sorted = gen.sort(declarations);
export const generatedCode = [
  "import * as t from 'io-ts';",
  "import { UUID } from 'io-ts-types/lib/UUID';\n",
  ...sorted.map(d => gen.printStatic(d)),
  ...sorted.map(d => gen.printRuntime(d))
].join('\n');

to generate the following code:

import * as t from 'io-ts';
import { UUID } from 'io-ts-types/lib/UUID';

export interface Category {
  id: UUID,
  children: Array<Category>
}
export const Category: t.RecursiveType<t.Type<Category>> = t.recursion('Category', () => t.type({
  id: UUID,
  children: t.array(Category)
}, 'Category'))

I get this error for the Category const:

Type 'RecursiveType<TypeC<{ id: UUIDC; children: ArrayC<RecursiveType<Type<Category, Category, unknown>, any, any, unknown>>; }>, any, any, unknown>' is not assignable to type 'RecursiveType<Type<Category, Category, unknown>, any, any, unknown>'.
  Type 'TypeC<{ id: UUIDC; children: ArrayC<RecursiveType<Type<Category, Category, unknown>, any, any, unknown>>; }>' is not assignable to type 'Type<Category, Category, unknown>'.
    Types of property 'encode' are incompatible.
      Type 'Encode<{ id: Branded<string, UUIDBrand>; children: any[]; }, { id: string; children: any[]; }>' is not assignable to type 'Encode<Category, Category>'.
        Type '{ id: string; children: any[]; }' is not assignable to type 'Category'.
          Types of property 'id' are incompatible.
            Type 'string' is not assignable to type 'Branded<string, UUIDBrand>'.
              Type 'string' is not assignable to type 'Brand<UUIDBrand>'.
const Category: t.RecursiveType<t.Type<Category, Category, unknown>, any, any, unknown>

The code above is generated using io-ts-codegen. I made a stackblitz project to show this error and explain it better:

https://stackblitz.com/edit/typescript-nzgxxh

Not sure about the cause or what to do and where to fix it.

gcanti commented 5 years ago

Not sure about the cause

@SAnDAnGE UUID is a brand codec, its type is

t.Type<UUID, string, unknown>

so the output type (string) is different from the resulting type (UUID), a codec base on UUID must account for that

import * as t from 'io-ts'
import { UUID } from 'io-ts-types/lib/UUID'

export interface Category {
  id: UUID
  children: Array<Category>
}
export interface CategoryOutput {
  id: string // <= !
  children: Array<CategoryOutput>
}
export const Category: t.RecursiveType<t.Type<Category, CategoryOutput>> = t.recursion(
  'Category',
  () =>
    t.type(
      {
        id: UUID,
        children: t.array(Category)
      },
      'Category'
    )
)
SAnDAnGE commented 5 years ago

Thank you for the quick answer @gcanti !

Is there any way to make this work with io-ts-codegen... or I would have to stop using it for branded codecs ?

gcanti commented 5 years ago

@SAnDAnGE https://github.com/gcanti/io-ts-codegen/pull/41

Could you please try it out by running npm i gcanti/io-ts-codegen#0.3.4-lib?

SAnDAnGE commented 5 years ago

Issue seems fixed for recursion, thank you for the quick fix and thank you for the awesome work you are doing !