lukeautry / tsoa

Build OpenAPI-compliant REST APIs using TypeScript and Node
MIT License
3.42k stars 489 forks source link

Recursive generic types infinitely recurse when generating routes #1547

Open jancastor opened 7 months ago

jancastor commented 7 months ago

Sorting

Steps to repro

1) Create a controller class file with the following contents

import { Get } from "api-external/src/rest/decorators/methods";

import { Controller, Route } from "tsoa";

/**
 * If T is an object, it returns T with an additional ID property
 * Otherwise we just return T
 */
type WithIdForObjectTypes<T> = T extends Record<string, any>
  ? AddIdToSubObjects<T> & { id: number }
  : T;

/**
 * This remaps a key-value mapping such that any object value has an additional ID property.
 * This is mutually recursive with WithIdForObjectTypes so that deeply nested objects also get the additional ID property.
 * e.g. { topLevelField: { subField: string } } -> { topLevelField: { subField: string, id: number } }
 *
 */
type AddIdToSubObjects<T extends Record<string, any>> = {
  [K in keyof T]: WithIdForObjectTypes<T[K]>;
};

/** Example case */
type MyObject = AddIdToSubObjects<{ topLevelField: { subField: string } }>;

@Route("example")
export class ExampleController extends Controller {
  @Get("example-endpoint")
  public async get() {
    const myObject: MyObject = {
      topLevelField: {
        subField: "foo",
        id: 1,
      },
    };

    return {
      myObject,
    };
  }
}

2) Run tsoa routes

Expected Behavior

I would expect this type to correctly resolve when running the tsoa cli (tsoa spec-and-routes).

Specifically, the endpoint GET /example/example-endpoint should have a response type of

{ 
  topLevelField: {
    subField: string,
    id: number
  }
}

Current Behavior

The tsoa cli is erroring out with the following error.

/** MANY MORE SIMILAR LOG LINES OMITTED FOR BREVITY **/
There was a problem resolving type of 'WithIdForObjectTypes<{"topLevelField": {"subField": string}}[string][string][string][string]>'.
There was a problem resolving type of 'WithIdForObjectTypes<{"topLevelField": {"subField": string}}[string][string][string]>'.
There was a problem resolving type of 'WithIdForObjectTypes<{"topLevelField": {"subField": string}}[string][string]>'.
There was a problem resolving type of 'WithIdForObjectTypes<{"topLevelField": {"subField": string}}[string]>'.
There was a problem resolving type of 'AddIdToSubObjects<{"topLevelField": {"subField": string}}>'.
Generate routes error.
 RangeError: Maximum call stack size exceeded
    at writeOrBuffer (node:internal/streams/writable:553:21)
    at _write (node:internal/streams/writable:490:10)
    at Writable.write (node:internal/streams/writable:494:10)
    at console.value (node:internal/console/constructor:304:16)
    at console.warn (node:internal/console/constructor:384:26)
    at calcReferenceType (/workspaces/obsidian/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:929:25)
    at TypeResolver.getReferenceType (/workspaces/obsidian/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:933:24)
    at TypeResolver.resolve (/workspaces/obsidian/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:513:36)
    at TypeResolver.resolve (/workspaces/obsidian/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:303:106)
    at TypeResolver.getTypeAliasReference (/workspaces/obsidian/node_modules/@tsoa/cli/dist/metadataGeneration/typeResolver.js:1019:126)

It seems like it's infinitely recursing when trying to resolve the given type.

Context (Environment)

Version of the library: 6.0.1 (also tried with 6.0.0) Version of NodeJS: 20.10.0

github-actions[bot] commented 7 months ago

Hello there jancastor 👋

Thank you for opening your very first issue in this project.

We will try to get back to you as soon as we can.👀