lukeautry / tsoa

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

[resolved but might be handles better?] Generic controller's methods #1510

Closed gdelory closed 8 months ago

gdelory commented 9 months ago

Hi all, I thought I would open a bug, then saw that what I was trying to do simply doesn't exist in OpenApi doc (here). To save some time to others, and because I searched the issues and there is nothing about generic controller's methods, I thought I would sill open/close this issue so other can find it šŸ˜‰

I was trying to migrate some basic express code we had which returns different types based on a query param. I could make the return type a union of both types, of course. But I tried to be smart and apply condition return type like I usually do with other normal (none tsoa) functions.

import { Controller, Get, Query, Route } from 'tsoa'

export type ConditionalQueryParam = 'true' | 'false' | undefined
export type Entry = { name: string }
export type ConditionalReturn<ID extends ConditionalQueryParam> = ID extends 'true' ? Entry[] : Entry

@Route('test')
export class TestController extends Controller {

  @Get('/')
  async get<MultipleParam extends ConditionalQueryParam>(
    @Query() multipleResults?: MultipleParam
  ): Promise<ConditionalReturn<MultipleParam>> {
    if (multipleResults == 'true') {
      return [{ name: 'multiple entry result '}] as ConditionalReturn<MultipleParam>
    } else {
      return { name: 'single entry result '} as ConditionalReturn<MultipleParam>
    }
  }
}

And this fails with the following error:

$ npm run build-api

> wfm@2.0.0 build-api
> rm -rf src/routes.ts && tsoa spec-and-routes

Generate routes error.
 GenerateMetadataError: No matching model found for referenced type MultipleParam. 
 in 'TestController.get'
    at /home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/methodGenerator.js:76:23
    at Array.map (<anonymous>)
    at MethodGenerator.buildParameters (/home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/methodGenerator.js:69:14)
    at MethodGenerator.Generate (/home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/methodGenerator.js:43:33)
    at /home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/controllerGenerator.js:46:41
    at Array.map (<anonymous>)
    at ControllerGenerator.buildMethods (/home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/controllerGenerator.js:46:14)
    at ControllerGenerator.Generate (/home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/controllerGenerator.js:35:27)
    at /home/gui/wfm/node_modules/@tsoa/cli/dist/metadataGeneration/metadataGenerator.js:191:41
    at Array.map (<anonymous>

Maybe instead of crashing, it would be possible to create a union type and output an anyOf in the swagger doc?

Anyway, if anyone encounter this, since openapi doesn't support it, it's just easier to switch back to a union type:

Promise<Entry | Entry[]>
github-actions[bot] commented 9 months ago

Hello there gdelory šŸ‘‹

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

We will try to get back to you as soon as we can.šŸ‘€

WoH commented 9 months ago

I think this can not really be well documented via OpenAPI either way. From a pure TS perspective, generic is fine here, but to compile down into a spec, the union is what you want.

So, in short, this is the best way right now.

gdelory commented 8 months ago

@WoH I completely agree with you, I just opened this issue to save time to future users having the same issue. In short, don't use conditional return type since it doesn't make sense (doesn't exist) anyway with OpenAPI, just stick to a union type for the return.

Closing this. Sorry, I should have done so right away, this was more meant as documentation for others šŸ˜‰ Thanks a lot for taking the time to answer, and your library is awesome, we had a really good experience with it so far, and I'll be happy to try to contribute if I have the opportunity in the future.