Skip to content

fix(ipa): preserve resource scope in operation IDs for trailing /operations paths - #1413

Draft
julius-jogela wants to merge 2 commits into
mainfrom
fix-operation-id-collection-scoped-operations
Draft

fix(ipa): preserve resource scope in operation IDs for trailing /operations paths#1413
julius-jogela wants to merge 2 commits into
mainfrom
fix-operation-id-collection-scoped-operations

Conversation

@julius-jogela

@julius-jogela julius-jogela commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

Operations resources with different scopes under the same parent generate the same operationId, so the collection-scoped and instance-scoped variants cannot coexist. This affects both the resource collection and the single resource:

Path Before After
/groups/{groupId}/clusters/operations listGroupClusterOperations listGroupClustersOperations
/groups/{groupId}/clusters/{clusterName}/operations listGroupClusterOperations listGroupClusterOperations (unchanged)
/groups/{groupId}/clusters/operations/{operationId} getGroupClusterOperation getGroupClustersOperation
/groups/{groupId}/clusters/{clusterName}/operations/{operationId} getGroupClusterOperation getGroupClusterOperation (unchanged)

Root cause

Verified in tools/spectral/ipa/rulesets/functions/utils/operationIdGeneration.js. generateOperationID filters path parameters out of the path before building the noun list, so both scopes reduce to the same nouns (groups, clusters, operations). The loop over non-final nouns then singularizes clusters unconditionally, erasing the only thing that distinguished them. The scope information exists only in the raw path, which the noun list has already discarded.

Fix

Keep the parent noun in plural form when the operations section is attached to a resource collection rather than to a single resource, following the generator's existing convention that plural versus singular parent nouns communicate collection versus instance scope. The existing singular form is retained for instance-scoped paths.

One helper, isCollectionScopedOperationsPath, covers both endpoints: it drops a trailing path parameter and then applies the same structural check. The noun index is unaffected by that strip because path parameters are already filtered out of the noun list.

Why the fix is intentionally narrow

The helper requires all of:

  • the trailing resource section is exactly operations, ignoring a single trailing path parameter (so .../operations/logs is untouched);
  • the section preceding operations is not a path parameter (this is what distinguishes the two scopes);
  • a parent section exists, so a bare /operations at the API root is unaffected;
  • the method is not a multi-word custom method, which appends its own trailing noun and would shift the parent's index.

General singularization behaviour is unchanged, and no rule function, fixture, or snapshot was modified to accommodate it.

Compatibility audit

Zero change to existing operationIds. I replayed main's generator against this branch's generator over all 319 paths in openapi/.raw/v2.yaml × 10 method names (3190 combinations), including the legacy empty-method form, and diffed the results: no differences. The only changed operationIds are the two intended ones in the table above.

There are currently no paths ending in /operations in the spec, so this only affects operations being introduced. A repo-wide grep for ClustersOperation and clusters/operations found no other occurrence — no fixtures, snapshots, or generated documentation encode the affected IDs.

Atlas CLI: Atlas CLI derives command names from operationId, but it lives in mongodb/mongodb-atlas-cli, not this repo, and this repo owns no Atlas CLI fixtures. Since no existing operationId changes, there is no command rename for it to absorb. The Go CLI in tools/cli only splits specs and passes operationIds through verbatim; it is unaffected.

IPA-104 / IPA-105 impact

Both xgen-IPA-105-valid-operation-id and xgen-IPA-104-valid-operation-id compare the spec's operationId against this shared generator, so they pick up the new behaviour automatically — no rule function or validation semantics changed.

Both published descriptions stated that only the last noun may be plural, which is now inaccurate for this case, so each gained one sentence documenting the exception (IPA-105 for the resource collection, IPA-104 for the single resource) and rulesets/README.md was regenerated via npm run gen-ipa-docs.

Tests

__tests__/utils/operationIdGeneration.test.js covers all four paths, explicit assertions that each scoped pair differs, negative cases for every narrowing condition, and a guard that unrelated paths (nested collection, single resource, multi-word custom method, legacy custom method) keep their existing IDs.

Command Result
npx jest .../operationIdGeneration.test.js 13 passed
npx jest IPA104 IPA105 14 suites, 61 passed
npx jest (full suite) 114 suites, 704 passed
npm run lint-js clean
npm run format-check clean
npm run ipa-validation no errors

Note for reviewers

Unrelated and not addressed here: generateOperationID throws TypeError: Cannot read properties of undefined on paths that reduce to zero nouns (for example /api/atlas/v2 itself), because singularize is called on undefined. This is pre-existing on main and my compatibility harness had to guard against it. Happy to file it separately.

…ations paths

A collection-scoped Operations resource and an instance-scoped Operations
resource under the same parent produced the same operation ID, because
generateOperationID removes path parameters before building the noun list
and then singularizes every non-final noun.

Keep the parent noun in plural form when the trailing 'operations' section
is attached to a resource collection, so the two resources get distinct IDs:

  /groups/{groupId}/clusters/operations                -> listGroupClustersOperations
  /groups/{groupId}/clusters/{clusterName}/operations  -> listGroupClusterOperations

The change is gated on a trailing 'operations' section with a non-parameter
parent, so no other operation IDs are affected.
…ation IDs

The sibling item-level Operations paths collided for the same reason as the
resource collection paths: path parameters are removed before the noun list is
built, so both reduced to the same nouns and the parent was singularized in
both cases.

Extend isCollectionScopedOperationsPath to drop a trailing path parameter
before the existing structural check, so the same collection- versus
instance-scope distinction applies to a single Operations resource:

  /groups/{groupId}/clusters/operations/{operationId}                -> getGroupClustersOperation
  /groups/{groupId}/clusters/{clusterName}/operations/{operationId}  -> getGroupClusterOperation

Document the exception in the IPA-104 valid-operation-id description, which
governs the Get method on a single resource.
@julius-jogela

Copy link
Copy Markdown
Collaborator Author

Extended this PR to also cover the item-level sibling collision I flagged in the original description.

/clusters/operations/{operationId} and /clusters/{clusterName}/operations/{operationId} both generated getGroupClusterOperation. Rather than a second special case, I extended isCollectionScopedOperationsPath to drop a trailing path parameter before its existing structural check, so one helper now covers both the Operations resource collection and a single Operations resource. Net addition is 4 lines of logic.

Re-ran the compatibility replay of main's generator versus this branch over all 319 spec paths x 10 methods (3190 combinations): still zero changes outside the four intended paths. IPA-104's published description needed the same one-sentence note as IPA-105, since it governs the Get method on a single resource; validation semantics are unchanged in both. Full suite 704 passed, lint/format/ipa-validation clean.

The open review question from the original description is now resolved, so nothing is outstanding.

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