fix(openapi): ignore operation tag matching the spec namespace in direct-to-IR converter to avoid nested docs group - #17646
Conversation
…to avoid nested docs group Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
AI Review Summary
Small, targeted fix: skip the first operation tag when it (case-insensitively) equals the spec namespace. Logic looks correct and matches #17643. One inconsistency worth noting: the new v3 snapshot still shows a nested video/video fernFilepath in subpackages.subpackage_video, which contradicts the PR description's claim that the nesting is gone.
- 🟡 1 warning(s)
- 🔵 1 suggestion(s)
To request another review, comment /ai-review on this pull request.
| "subpackage_video": { | ||
| "name": "video", | ||
| "fernFilepath": { | ||
| "allParts": [ | ||
| "video", | ||
| "video" | ||
| ], | ||
| "packagePath": [ | ||
| "video" | ||
| ], | ||
| "file": "video" | ||
| }, |
There was a problem hiding this comment.
🟡 warning
This committed snapshot still shows the nested path: subpackage_video.fernFilepath.allParts is ["video", "video"] (and service_video.name.fernFilepath.allParts is ["video"] with file: "video", vs. baseline's packagePath: ["video"]). The PR description says the post-fix v3 snapshot should contain only subpackage_video/service_video matching baseline — either the snapshot was regenerated before the fix, or the fix doesn't fully eliminate the duplicate segment for the docs path. Worth confirming which.
| const tag = this.operation.tags?.[0]; | ||
| // The namespace is applied to every endpoint in the spec, so a tag that | ||
| // merely restates it must not become an additional sub-group. | ||
| const tag = this.operation.tags?.find((tag) => !this.isNamespaceTag(tag)); |
There was a problem hiding this comment.
🔵 suggestion
Shadowing the outer tag binding name inside the predicate is a bit noisy; rename the param (e.g. candidate) for readability. Also consider whether tags should be trimmed before comparison — " Video" won't match video today.
Docs Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on
Docs generation runs |
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
…ma types in a package Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
AI Review Summary
Two targeted fixes to avoid double-nesting the namespace in the direct OpenAPI→IR converter, plus a new fixture and snapshot updates. Logic looks reasonable; main concerns are behavioral edge cases: skipping a namespace-matching tag can silently promote the second tag to the group name (changing grouping for multi-tag operations), and the new v3 snapshot still shows subpackage_video with allParts: ["video","video"], which suggests the nesting isn't fully gone in that pipeline.
- 🟡 2 warning(s)
- 🔵 1 suggestion(s)
To request another review, comment /ai-review on this pull request.
| const tag = this.operation.tags?.[0]; | ||
| // The namespace is applied to every endpoint in the spec, so a tag that | ||
| // merely restates it must not become an additional sub-group. | ||
| const tag = this.operation.tags?.find((tag) => !this.isNamespaceTag(tag)); |
There was a problem hiding this comment.
🟡 warning
find changes behavior for multi-tag operations: previously only tags[0] mattered; now if tags = ["Video", "Streams"] with namespace: video, the group becomes Streams instead of flattening into the namespace. That's a grouping change for specs that aren't the bug being fixed. If the intent is "drop the tag only when it restates the namespace", prefer checking just the first tag:
| const tag = this.operation.tags?.find((tag) => !this.isNamespaceTag(tag)); | |
| const firstTag = this.operation.tags?.[0]; | |
| const tag = firstTag != null && this.isNamespaceTag(firstTag) ? undefined : firstTag; |
If promoting the next tag is intentional, worth a comment/test covering the multi-tag case.
| "video" | ||
| ], | ||
| "file": "video" | ||
| }, |
There was a problem hiding this comment.
🟡 warning
This v3 snapshot still has subpackage_video with fernFilepath.allParts: ["video", "video"] under a rootPackage whose filepath is already ["video"], i.e. the namespace still appears twice in this pipeline (unlike the baseline snapshot, which is flat). Is that expected for the v3-sdks path, or does the openapi-ir-to-fern side still need the same fix? Worth confirming this doesn't reproduce the nested docs group.
| namespace: this.context.namespace | ||
| }); | ||
| const groupPackage = this.getOrCreatePackage({ group }); | ||
| const groupPackage = this.getOrCreatePackage({ group: [] }); |
There was a problem hiding this comment.
🔵 suggestion
getOrCreatePackage prepending context.namespace internally is now load-bearing but invisible here. A one-line comment (// getOrCreatePackage already prefixes context.namespace) would save the next person from "fixing" this back.
Description
Linear ticket: Refs (none)
The direct OpenAPI-to-IR importer (
openapi-to-ir, used byfern generate --docs/fern docs dev/fern fdr --from-openapi) rendered a nestedVideo > Videogroup for a spec withnamespace: videowhose operations are taggedVideo. The SDK path (openapi-ir-to-fern) was already fixed in #17643 (CLI 5.113.1); docs still nested because it goes through a different converter. Two independent causes, both fixed here:AbstractOperationConverter.computeGroupNameFromTagAndOperationIdpicked the first tag unconditionally, so a tag equal to the namespace (case-insensitively) became a sub-group under the namespace package.AbstractSpecConverter.addTypeToPackagepassedgetGroup({ groupParts: [], namespace })(=[namespace]) intogetOrCreatePackage, which itself prependscontext.namespace→ every namespaced schema type was placed in a phantomsubpackage_<ns>/<ns>package. This alone produced the extra nav group even when no tag matched.Result:
namespace: video+ tagVideo→subpackages: { subpackage_video: [] }, endpoints and types directly invideo. Unnamespaced specs are unaffected (getOrCreatePackage([])returnsrootPackageas before).Changes Made
AbstractOperationConverter: skip operation tags that matchcontext.namespacecase-insensitively when computing the group.AbstractSpecConverter.addTypeToPackage: stop double-prefixing the namespace; types go into the namespace package itself.namespaces-tag-matches-namespace(tagVideo,namespace: video, one schema type) + snapshots;namespaces-basic/namespaces-merge-collisionsnapshots lose their phantomsubpackage_<ns>/<ns>type packages.packages/cli/cli/changes/unreleased/fix-v3-namespace-tag-nesting.yml(fix).Testing
pnpm turbo run test --filter "...@fern-api/v3-importer-commons"green (v3-importer-tests 338/338, openapi-to-ir, lazy-fern-workspace, register, ir-migrations, …); only failure isete-tests > ir contains fdr definitionidwhich requiresFERN_ORG_TOKEN_DEVlocally.pnpm fern-dev:build), ranfern fdr --from-openapiandfern docs devwithFERN_NO_VERSION_REDIRECTION=trueon a minimal Video repro: released 5.113.1 showsAPI Reference > Video > Video; this branch shows a singleVideogroup.Link to Devin session: https://app.devin.ai/sessions/37f61a97e3cb4d66a6f2a69a4bd44246
Open in Devin Desktop: https://app.devin.ai/desktop/session/37f61a97e3cb4d66a6f2a69a4bd44246?variant=devin