Skip to content

fix(openapi): ignore operation tag matching the spec namespace in direct-to-IR converter to avoid nested docs group - #17646

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1788462437-v3-namespace-tag-nesting
Open

fix(openapi): ignore operation tag matching the spec namespace in direct-to-IR converter to avoid nested docs group#17646
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1788462437-v3-namespace-tag-nesting

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs (none)

The direct OpenAPI-to-IR importer (openapi-to-ir, used by fern generate --docs / fern docs dev / fern fdr --from-openapi) rendered a nested Video > Video group for a spec with namespace: video whose operations are tagged Video. 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:

  1. AbstractOperationConverter.computeGroupNameFromTagAndOperationId picked the first tag unconditionally, so a tag equal to the namespace (case-insensitively) became a sub-group under the namespace package.
  2. AbstractSpecConverter.addTypeToPackage passed getGroup({ groupParts: [], namespace }) (= [namespace]) into getOrCreatePackage, which itself prepends context.namespace → every namespaced schema type was placed in a phantom subpackage_<ns>/<ns> package. This alone produced the extra nav group even when no tag matched.
-const group = this.context.getGroup({ groupParts: [], namespace: this.context.namespace });
-const groupPackage = this.getOrCreatePackage({ group });
+const groupPackage = this.getOrCreatePackage({ group: [] });

Result: namespace: video + tag Videosubpackages: { subpackage_video: [] }, endpoints and types directly in video. Unnamespaced specs are unaffected (getOrCreatePackage([]) returns rootPackage as before).

Changes Made

  • AbstractOperationConverter: skip operation tags that match context.namespace case-insensitively when computing the group.
  • AbstractSpecConverter.addTypeToPackage: stop double-prefixing the namespace; types go into the namespace package itself.
  • New v3 fixture namespaces-tag-matches-namespace (tag Video, namespace: video, one schema type) + snapshots; namespaces-basic / namespaces-merge-collision snapshots lose their phantom subpackage_<ns>/<ns> type packages.
  • Changelog packages/cli/cli/changes/unreleased/fix-v3-namespace-tag-nesting.yml (fix).
  • Updated README.md generator (N/A)

Testing

  • Unit tests added/updated — 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 is ete-tests > ir contains fdr definitionid which requires FERN_ORG_TOKEN_DEV locally.
  • Manual testing completed — rebuilt dev CLI (pnpm fern-dev:build), ran fern fdr --from-openapi and fern docs dev with FERN_NO_VERSION_REDIRECTION=true on a minimal Video repro: released 5.113.1 shows API Reference > Video > Video; this branch shows a single Video group.

docs nav after fix

Link to Devin session: https://app.devin.ai/sessions/37f61a97e3cb4d66a6f2a69a4bd44246
Open in Devin Desktop: https://app.devin.ai/desktop/session/37f61a97e3cb4d66a6f2a69a4bd44246?variant=devin


Devin Review

…to avoid nested docs group

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@nitpickybot nitpickybot Bot 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.

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.

Comment on lines +429 to +440
"subpackage_video": {
"name": "video",
"fernFilepath": {
"allParts": [
"video",
"video"
],
"packagePath": [
"video"
],
"file": "video"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 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.

@devin-ai-integration devin-ai-integration Bot changed the title fix(openapi-to-ir): ignore operation tag matching the spec namespace to avoid nested docs group fix(openapi): ignore operation tag matching the spec namespace in direct-to-IR converter to avoid nested docs group Sep 3, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-09-03T05:10:38Z).

Fixture main PR Delta
docs 269.1s (n=5) 300.6s (35 versions) +31.5s (+11.7%)

Docs generation runs fern generate --docs --preview end-to-end against the benchmark fixture with 35 API versions (each version: markdown processing + OpenAPI-to-IR + FDR upload).
Delta is computed against the nightly baseline on main.
Baseline from nightly run(s) on main (latest: 2026-09-03T05:10:38Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-09-03 20:32 UTC

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-09-03T05:10:38Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 82s (n=5) 118s (n=5) 55s -27s (-32.9%)
go-sdk square 149s (n=5) 311s (n=5) 136s -13s (-8.7%)
java-sdk square 242s (n=5) 300s (n=5) 203s -39s (-16.1%)
php-sdk square 74s (n=5) N/A 60s -14s (-18.9%)
python-sdk square 152s (n=5) 254s (n=5) 142s -10s (-6.6%)
ruby-sdk-v2 square 92s (n=5) 142s (n=5) 68s -24s (-26.1%)
rust-sdk square 208s (n=5) 228s (n=5) 185s -23s (-11.1%)
swift-sdk square 80s (n=5) 464s (n=5) 49s -31s (-38.8%)
ts-sdk square 178s (n=5) 184s (n=5) 126s -52s (-29.2%)

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 fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-09-03T05:10:38Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-09-03 20:33 UTC

…ma types in a package

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@nitpickybot nitpickybot Bot 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.

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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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:

Suggested change
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"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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: [] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 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.

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