Skip to content

feat(@ApiExtension): When used on a controller it applies to all methods #3485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

drewish
Copy link
Contributor

@drewish drewish commented Jun 16, 2025

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

If you try to use the @ApiExtension() decorator at the controller level it has no effect on the OpenAPI file. It only has an effect on methods.

So using it at the controller level like this would have no effect on the create operation.

@ApiExtension('x-entitlements', ['api-access'])
@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  @ApiOperation({ summary: 'Create cat' })
  async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    // ...
  }
}

Issue Number: N/A

What is the new behavior?

Now when @ApiParam() decorator is used on a controller the extension property is added to all the operations implemented by the controller.

The above example would apply the extension to the operation:

    "/api/cats": {
      "post": {
        "operationId": "CatsController_create",
// ...
        "summary": "Create cat",
        "tags": [
          "cats",
          "create cats"
        ],
        "x-entitlements": [
          "api-access"
        ]
      },

If you apply the same extension decorator at the method level it will override the controller level value. I spent a little time trying to merge the values but it added quite a bit of complexity and seemed like It could give some surprising results.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@drewish
Copy link
Contributor Author

drewish commented Jun 16, 2025

  • Docs have been added / updated (for bug fixes / features)

Docs are a separate repo right? I'm happy to update those if this looks like something that will accepted.

@drewish drewish force-pushed the feat/controller-level-extensions branch 2 times, most recently from 2ab32e8 to a906676 Compare June 18, 2025 14:13
@drewish drewish force-pushed the feat/controller-level-extensions branch from a906676 to 218c820 Compare June 18, 2025 14:22
"x-schema-extension": {
"test": "test"
},
"x-schema-extension-multiple": {
"test": "test*2"
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of these extensions changed but I modified the values to make it clear they aren't affected.

Comment on lines +47 to +54
.map((propertyKey) =>
Object.getOwnPropertyDescriptor(target.prototype, propertyKey)
)
.filter((methodDescriptor) => methodDescriptor !== undefined)
.filter((methodDescriptor) =>
Reflect.hasMetadata(METHOD_METADATA, methodDescriptor.value)
)
.map((methodDescriptor) => methodDescriptor.value);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking back at this i'm realizing we could consolidate this down a bit.

Suggested change
.map((propertyKey) =>
Object.getOwnPropertyDescriptor(target.prototype, propertyKey)
)
.filter((methodDescriptor) => methodDescriptor !== undefined)
.filter((methodDescriptor) =>
Reflect.hasMetadata(METHOD_METADATA, methodDescriptor.value)
)
.map((methodDescriptor) => methodDescriptor.value);
.map((propertyKey) =>
Object.getOwnPropertyDescriptor(target.prototype, propertyKey)?.value
)
.filter((methodDescriptor) =>
methodDescriptor !== undefined && Reflect.hasMetadata(METHOD_METADATA, methodDescriptor)
);

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.

1 participant