Skip to content

feat: add support for additionalOperations in oas32 - #11090

Open
cka121 wants to merge 21 commits into
mainfrom
feat/oss-890-additional-operation-support-in-oas32
Open

cka121 wants to merge 21 commits into
mainfrom
feat/oss-890-additional-operation-support-in-oas32

Conversation

@cka121

@cka121 cka121 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Adds OpenAPI 3.2 Path Item additionalOperations support to Swagger UI. Custom HTTP methods (e.g. COPY, LINK, Search-Pets) are discovered and rendered as first-class operations under paths, callbacks and webhooks, with the exact method token shown as the badge label.

Description

This PR builds on the community contribution by @cmmoran in #10956 and keeps the original commits. On top of that work, it addresses the maintainer review comments, fixes the gaps found during review, and adds the requested Cypress coverage.

From the original contribution (@commoran):

Adds OpenAPI 3.2 Path Item additionalOperations support to Swagger UI. Custom operation methods are discovered and rendered as first-class operations, including their request parameters, request bodies, responses, deep links, validation state, and operation-specific metadata.

Custom methods retain the exact method token from the OpenAPI document. Standard methods continue to use the existing built-in behavior, while custom methods receive neutral styling rather than being added as hard-coded method names.

Added in this PR:

Review feedback: custom methods use a single neutral opblock-custom class (light and dark themes) and reuse OPERATION_METHODS instead of a local list. Removed the unused opblock-summary-{method} /bold-label-{method}classes and switched to includes().

  • Exact badge labels: built-in methods are still shown uppercase. Custom tokens are now shown exactly as written (Search-Pets instead of SEARCH-PETS).
  • Fixed-field duplicates: additionalOperations keys that duplicate a fixed field (POST, post, HEAD, QUERY…) are ignored regardless of case, as the spec requires. operationSpecPath always resolves fixed methods to their direct path, so an invalid entry can't take over the real operation.
  • Resolved discovery: custom methods are discovered from specJsonWithResolvedSubtrees, so operations inside $ref'd Path Items aren't filtered out.
  • Callbacks and webhooks: OAS 3.2-only wrappers (callbacksOperations, selectWebhooksOperations, webhooks) collect additionalOperations from nested Path Items through a shared pathItemOperations helper. The webhooks section in base.jsx now renders for isOAS31 || isOAS32.
  • Dependency: swagger-client is updated to 3.38.2, which provides additionalOperations request execution.
  • Docs: supportedSubmitMethods documents exact, case-sensitive custom tokens.
  • Tests: combined @cmmoran's Cypress suite (rendering, $ref'd body and responses, deep link after reload, required-body validation, and LIST / X-Search execution through the released swagger-client) with the review follow-ups (callbacks, webhooks, ignored fixed-field duplicates, operation-level servers and security, scoped parameter state, default allowlist, COPY execution) in oas32-additional-operations.cy.js and one shared fixture. Updated his assertions for the opblock-custom class and exact labels.

Known limitation: custom operations without an operationId render correctly but can't be run from Try it out. With swagger-client 3.38.2, buildRequest fails with Operation undefined not found when it only has the path and method. This needs a follow-up in swagger-js; the Cypress test for it is included as it.skip.

Motivation and Context

OAS 3.2 allows arbitrary HTTP methods under additionalOperations on the Path Item Object (spec). Swagger UI previously left these operations out and couldn't resolve their data through the normal selector paths.

Supersedes #10956 Depends on swagger-api/swagger-client#4238 (released in swagger-client 3.38.2)

How Has This Been Tested?

  • npm run lint-errors: passed
  • npm run test:unit -- --runInBand: passed, skipped
  • OAS 3.2 unit suites (test/unit/core/plugins/oas32): 32/32 passed
  • Cypress (npm run cy:start + cy:run, Electron):
  • oas32-additional-operations.cy.js: 16 passed, 1 pending (the no-operationId execution test above)
  • oas32-query-operation.cy.js: all passed (no regression)
  • Cypress coverage: custom operations rendered next to standard ones; exact labels; $ref'd parameters, responses and request bodies; operation-level servers and security; deep links; parameter state and validation kept per operation; the default submit allowlist; callbacks; exactly one webhook block; fixed-field duplicates ignored; COPY executed with its exact method token and headers.
  • Environment: macOS, Node 24.21.0, Cypress 14.2.0.

Screenshots (if appropriate):

Screenshot 2026-10-08 at 5 01 37 PM

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Referenced required request bodies can bypass validation, and several new files violate repository conventions.

6 open findings
What changed in this PR

Adds OAS 3.2 additionalOperations support across rendering, resolution, callbacks, webhooks, execution, and styling.

Changes:

  • Discovers custom methods while rejecting fixed-field duplicates.
  • Adds custom operation rendering, styling, resolution, and nested Path Item support.
  • Adds documentation and comprehensive unit/E2E coverage.
File Description
docs/​usage/​configuration.md Documents custom submit methods.
src/​core/​components/​layouts/​base.jsx Renders OAS 3.2 webhooks.
src/​core/​components/​operation-summary-method.jsx Preserves custom method casing.
src/​core/​components/​operation-summary.jsx Removes method-specific summary classes.
src/​core/​components/​operation.jsx Applies neutral custom styling.
src/​core/​components/​overview.jsx Preserves custom labels in links.
src/​core/​plugins/​oas3/​selectors.js Resolves custom-operation request bodies.
src/​core/​plugins/​oas32/​index.js Registers new selector wrappers.
src/​core/​plugins/​oas32/​spec-extensions/​wrap-selectors.js Handles custom methods, callbacks, and webhooks.
src/​core/​plugins/​spec/​reducers.js Validates custom-operation parameters.
src/​core/​plugins/​spec/​selectors.js Discovers and resolves additional operations.
src/​style/​_dark-mode.scss Adds dark-mode custom styling.
src/​style/​_layout.scss Adds custom operation styling.
src/​style/​_variables.scss Defines the custom method color.
test/​e2e-cypress/​e2e/​features/​oas32/​oas32-additional-operations.cy.js Adds end-to-end coverage.
test/​e2e-cypress/​static/​documents/​features/​oas32-additional-operations.yaml Provides the E2E fixture.
test/​unit/​components/​additional-operations.jsx Tests labels and styling.
test/​unit/​core/​plugins/​oas32/​additional-path-items.test.js Tests nested Path Items.
test/​unit/​core/​plugins/​oas32/​query-operation-rendering.test.js Tests custom method discovery.
test/​unit/​core/​plugins/​oas32/​spec-extensions/​wrap-selectors.test.js Updates wrapper setup.
test/​unit/​core/​plugins/​spec/​assets/​petstore.json Reformats the existing fixture.
test/​unit/​core/​plugins/​spec/​selectors.js Tests discovery, paths, and request bodies.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/core/plugins/oas3/selectors.js Outdated
Comment thread src/style/_dark-mode.scss Outdated
Comment thread src/style/_variables.scss Outdated
@@ -0,0 +1,418 @@
/**
Comment thread test/unit/components/additional-operations.tsx
Comment thread test/unit/core/plugins/oas32/additional-path-items.test.ts
Comment thread src/core/plugins/spec/selectors.js Outdated
const DEFAULT_TAG = "default"

const OPERATION_METHODS = [
export const OPERATION_METHODS = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Anything that we export from this file will be registered as a selector because the plugin imports everything through import * as selectors from "./selectors” and adds to the system with selectors: { ...selectors }. Should we move OPERATION_METHODS and isFixedOperationMethod to some util file instead? Could be in src/core/utils/operation-methods.ts or something similar instead, as it's imported by other plugins as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

okay

* Entries that duplicate a fixed field (in any case) are invalid per OAS 3.2
* and must be ignored.
*/
const isFixedFieldMethod = (method) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this different from isFixedOperationMethod? Could we reuse that instead?

*
* Reference: https://spec.openapis.org/oas/v3.2.0.html#path-item-object
*/
export const validOperationMethods = createOnlyOAS32SelectorWrapper(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder how often this selector is called - would it make sense to memoize it with createSelector, if it's possible and will still work as expected?

Similar with selectWebhooksOperations - I see that it was memoized for OAS 3.1.

.specJsonWithResolvedSubtrees()
.get("paths")

if (paths?.forEach) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would this work?

Suggested change
if (paths?.forEach) {
if (Map.isMap(paths)) {

paths.forEach((pathItem) => {
const additionalOperations = pathItem?.get?.("additionalOperations")

if (additionalOperations?.forEach) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above:

Suggested change
if (additionalOperations?.forEach) {
if (Map.isMap(additionalOperations)) {

})),
specSelectors: {
specJsonWithResolvedSubtrees: jest.fn(() =>
Map({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would one fromJS work instead of nesting Maps, e.g.

  fromJS({
     paths: {
        "/pets": {

Similar in other tests.

})

describe("state and validation", () => {
it("keeps parameter values and validation scoped to the custom operation", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In e2e tests, should we not be using inputs and execute try it out to check if validation works?

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.

5 participants