Skip to content

fix(plugin): restore default 2xx response when only error Api*Response decorators present#3863

Closed
yogeshwaran-c wants to merge 2 commits into
nestjs:masterfrom
yogeshwaran-c:fix/plugin-2xx-response-regression
Closed

fix(plugin): restore default 2xx response when only error Api*Response decorators present#3863
yogeshwaran-c wants to merge 2 commits into
nestjs:masterfrom
yogeshwaran-c:fix/plugin-2xx-response-regression

Conversation

@yogeshwaran-c

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix.

What is the current behavior?

A regression introduced in 11.2.7 (by #3803, fixing #1639) causes the plugin to drop the auto-inferred 2xx response whenever a controller method is decorated with only error @Api*Response decorators. The hasExplicitApiResponseDecorator guard added in that PR treats any decorator whose name matches Api*Response as an explicit response, so handlers annotated with only @ApiUnauthorizedResponse(), @ApiNotFoundResponse(), @ApiResponse({ status: 500 }), etc. lose their auto-inferred 201/200. The resulting spec advertises only the error status codes, which breaks downstream client generators (NSwag, openapi-generator, etc.).

Before 11.2.7:

/session/start:
  post:
    responses:
      '201': { description: '' }
      '401': { description: '' }

After 11.2.7 (broken):

/session/start:
  post:
    responses:
      '401': { description: '' }

Closes #3862

What is the new behavior?

The plugin now treats a decorator as an explicit success declaration only when:

  • it is a 2xx-coded Api*Response alias (e.g. ApiOkResponse, ApiCreatedResponse, ApiNoContentResponse, ApiAcceptedResponse, and the other 2xx aliases derived from HttpStatus),
  • it is ApiDefaultResponse, or
  • it is a plain @ApiResponse whose status argument is a 2xx numeric literal, '2XX', or 'default'.

Non-success decorators (3xx/4xx/5xx, including @ApiFoundResponse, @ApiUnauthorizedResponse, and friends) no longer suppress the inferred 2xx. The auto-generated default is emitted alongside the explicit error entries, and the OpenAPI spec once again contains both success and error status codes.

Additional context

  • The set of 2xx alias names is derived from HttpStatus at module-load time, mirroring how the decorators themselves are generated in lib/decorators/api-response.decorator.ts, so any future additions stay in sync automatically.
  • When the status argument of @ApiResponse is a non-literal expression (e.g. HttpStatus.OK), the plugin cannot evaluate it at compile time; to preserve the original intent of Adding @ApiFoundResponse on a dynamic redirect method still renders a 200 response code in the OpenAPI contract #1639, such expressions continue to be treated as explicit success declarations.
  • A dedicated regression fixture (test/plugin/fixtures/app.controller-error-only-api-response.ts) exercises the error-only scenarios from the issue: a POST with @ApiUnauthorizedResponse, a GET with multiple error decorators, and a @ApiResponse({ status: 500 }). The existing #1639 fixture is updated so the 302 redirect handler now carries the restored default alongside its explicit @ApiFoundResponse.
  • npm test (274 unit tests) and npm run test:e2e (114 tests) pass locally.

…e decorators present

PR nestjs#3803 added a `hasExplicitApiResponseDecorator` guard that suppressed the
auto-generated default response whenever any `Api*Response` decorator (or
`@ApiResponse`) appeared on a handler. The check did not distinguish 2xx
from non-2xx responses, so handlers decorated with only error responses
(`@ApiUnauthorizedResponse`, `@ApiNotFoundResponse`,
`@ApiResponse({ status: 500 })`, etc.) silently lost their auto-inferred
201/200, producing specs that advertised only error status codes.

The check is narrowed to treat a decorator as an explicit success
declaration only when it is a 2xx `Api*Response` alias, `ApiDefaultResponse`,
or an `ApiResponse` whose status argument is a 2xx numeric literal,
`'2XX'`, or `'default'`. Non-success decorators (3xx, 4xx, 5xx) no longer
suppress the inferred 2xx. A regression fixture with error-only decorators
is added alongside updates to the existing fixtures that already implied
the restored behavior.

Fixes nestjs#3862
@PeterTheOne

Copy link
Copy Markdown

Heads up — this re-introduces #1639. The 2xx-only filter excludes ApiFoundResponse (302), so @Get @Redirect @ApiFoundResponse gets a spurious 200 added back. The fixture diff in this PR asserts exactly that:

     (0, swagger_1.ApiFoundResponse)({ description: 'Redirects to a URL' }),
-    (0, common_1.Redirect)()
+    (0, common_1.Redirect)(),
+    openapi.ApiResponse({ status: 200, type: Object })

The 2xx-only guard from the previous commit re-introduced nestjs#1639 for
redirect handlers: @reDIrect + @ApiFoundResponse (302) was getting a
spurious 200 auto-added because 3xx decorators were not recognised as
explicit response declarations. Broadens the non-error decorator set
(and the @apiresponse status-argument check) to cover 2xx and 3xx so
the auto-inferred success response is suppressed for redirect-style
handlers while error-only decorators still allow the default to be
emitted (the nestjs#3862 behaviour).
@yogeshwaran-c

Copy link
Copy Markdown
Contributor Author

Thanks @PeterTheOne — good catch, the fixture diff was indeed showing the bug, not the fix. Pushed f00a9fc.

The underlying issue was that the "explicit declaration" guard only recognised 2xx decorators. For a handler with @Get + @Redirect + @ApiFoundResponse (302), none of those were 2xx-coded, so the plugin fell back to the auto-inferred 200 — reproducing #1639 for the 3xx case.

Fix: widen the non-error decorator set (renamed SUCCESS_API_RESPONSE_DECORATORSNON_ERROR_API_RESPONSE_DECORATORS) and the @ApiResponse({ status }) check to cover 2xx and 3xx. Redirect-style handlers now suppress the auto 200; error-only decorators (4xx/5xx) still allow the default to be emitted, preserving the #3862 behaviour.

The #1639 fixture now asserts the corrected output — no auto-generated ApiResponse({ status: 200, type: Object }) on the redirect handler — and I added a @ApiResponse({ status: 301 }) case to exercise the numeric-literal branch for 3xx too.

@kamilmysliwiec

Copy link
Copy Markdown
Member

fixed #3867

@yogeshwaran-c

Copy link
Copy Markdown
Contributor Author

Thanks @kamilmysliwiec — happy this is fixed via #3867. Closing the loop on this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression in 11.2.7: plugin drops auto-inferred 2xx response when only error @Api*Response decorators are present (PR #3803)

3 participants