fix(plugin): keep auto-inferred default response when only error Api*Response decorators are present#3867
Conversation
|
|
||
| type ClassMetadata = Record<string, ts.ObjectLiteralExpression>; | ||
|
|
||
| function isSuccessOrRedirectApiResponseArg(decorator: ts.Decorator): boolean { |
There was a problem hiding this comment.
nit: can we move it to another dedicated file, or at least make it a method of the ControllerClassVisitor class
There was a problem hiding this comment.
Done — moved it to a private method on ControllerClassVisitor in fe80eb3. Felt like overkill to spin up a dedicated file for a ~20-line helper with one call site, but happy to extract further if you'd prefer.
|
just checking to see if you're still willing to work on this PR @PeterTheOne, or if you would rather want us to focus on #3863 instead |
|
@kamilmysliwiec I did your nit comment. I don't mind if we go with the other PR, I just want this fixed asap :) |
|
excellent @PeterTheOne thank you! would you mind resolving PR conflicts? if so, ill get this merged and released asap |
…Response decorators are present PR nestjs#3803 added a guard that suppresses the auto-inferred default response whenever any `Api*Response` decorator is present on a handler. The guard doesn't distinguish success (2xx) from error (4xx/5xx) decorators, so a handler with only `@ApiUnauthorizedResponse()` (or any other error-status decorator) loses its auto-inferred 2xx in the OpenAPI spec. Downstream client generators (NSwag, openapi-generator, ...) then treat the real 2xx response as an exception at runtime. Scope the guard to decorators whose status code is below 400 so error factories no longer suppress the default. The success/redirect case the original PR fixed (`@ApiFoundResponse` on a `@Redirect()` handler) continues to work — 302 is still below 400. Status codes are derived from the decorator name via the same `Api${PascalCase(HttpStatus key)}Response` convention the factories are generated from in `api-response.decorator.ts`. Unknown names (`ApiDefaultResponse`, user-defined) fall back to the previous behavior (treated as explicit). Closes nestjs#3862
…t/500 cases
Extend the fix so that a plain `@ApiResponse({ status: N })` is also classified
by its status code: a 4xx/5xx status no longer suppresses the auto-inferred 2xx.
Non-literal `status` expressions (e.g. `HttpStatus.OK`) can't be evaluated at
compile time and continue to be treated as explicit, preserving pre-existing
behavior.
Extend the regression fixture with:
- `@Post @apiresponse({ status: 500 })` — asserts the default 201 is
auto-added (the new arg-inspection case).
- `@Get @reDIrect @ApiFoundResponse` — asserts no default is added, so the
fix continues to honor nestjs#1639 for redirect handlers.
The existing `app.controller` fixture had baked in the 11.2.7 behavior on the
`create()` handler (multiple error-status `@ApiResponse` decorators); update it
to the restored pre-11.2.7 output (default 201 alongside the error entries).
…thod Address PR review nit: move the standalone helper into ControllerClassVisitor as a private method, since it's only used by addDecoratorToNode and doesn't need its own module.
fe80eb3 to
f01f6aa
Compare
|
Rebased onto current master, conflicts resolved (just a test-placement collision with the new |
|
LGTM |
Summary
Fixes #3862 — regression introduced in 11.2.7 via PR #3803.
PR #3803 added a guard that suppresses the auto-inferred default response whenever any
Api*Responsedecorator is present. The guard doesn't distinguish success (2xx) from error (4xx/5xx) decorators, so a handler with only@ApiUnauthorizedResponse()(or any other error-status decorator) loses its auto-inferred 2xx in the OpenAPI spec. Downstream client generators (NSwag, openapi-generator, ...) then treat the real 2xx response as an exception at runtime.This PR scopes the guard to decorators whose status code is below 400 so error factories no longer suppress the default. The redirect case the original PR fixed (
@ApiFoundResponseon a@Redirect()handler) continues to work — 302 is still below 400.Approach
Named
Api*Responsefactories — status codes are derived from the decorator name via the sameApi${PascalCase(HttpStatus key)}Responseconvention the factories are generated from inapi-response.decorator.ts. Unknown names (ApiDefaultResponse, user-definedApi*Response) fall back to the previous behavior (treated as explicit), so no existing explicit-response pattern regresses.Mirrors the forward-direction idiom in
api-response.decorator.ts:148:Plain
@ApiResponse({ status })— thestatusargument is inspected: numeric literals (status: 500), the shorthand strings'1XX'/'2XX'/'3XX', and'default'are classified by their code. Non-literal expressions (e.g.HttpStatus.OK) can't be evaluated at compile time and keep the pre-PR behavior (treated as explicit).Behavior matrix
@Post+@ApiUnauthorizedResponse@Get+@ApiNotFoundResponse@Post+@ApiResponse({ status: 500 })@Post+@ApiCreatedResponse+@ApiUnauthorizedResponse@Get @Redirect @ApiFoundResponse(#1639)Tests
test/plugin/fixtures/app.controller-api-response-error.tscovering five scenarios:@Post+@ApiUnauthorizedResponsealone@Get+@ApiNotFoundResponsealone@Post+@ApiCreatedResponse+@ApiUnauthorizedResponse(explicit success, default still suppressed)@Post+@ApiResponse({ status: 500 })alone (arg-inspection case)@Get+@Redirect+@ApiFoundResponse(Adding @ApiFoundResponse on a dynamic redirect method still renders a 200 response code in the OpenAPI contract #1639 preservation)controller-class-visitor.spec.ts(named after Regression in 11.2.7: plugin drops auto-inferred 2xx response when only error @Api*Response decorators are present (PR #3803) #3862).app.controller-api-response.ts) still passes — redirect case preserved.app.controllerfixture updated: itscreate()handler uses multiple error-status@ApiResponsedecorators and the expected output had baked in the 11.2.7 regression. Now expects the restored pre-11.2.7 behavior (default 201 alongside the error entries).npm run lintclean on changed files.prettier --checkclean on changed files.Disclaimers