Skip to content

feat(ir): extract per-content-type OAS examples into endpoint-level v2Examples#16718

Open
adidavid014 wants to merge 4 commits into
mainfrom
devin/1782410604-per-content-type-examples
Open

feat(ir): extract per-content-type OAS examples into endpoint-level v2Examples#16718
adidavid014 wants to merge 4 commits into
mainfrom
devin/1782410604-per-content-type-examples

Conversation

@adidavid014

@adidavid014 adidavid014 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs FER-11417

When an OpenAPI operation declares multiple content types (e.g. application/json and application/ld+json) that share the same schema but define distinct per-content-type examples, those examples were lost in the CLI → FDR pipeline. This PR preserves them by:

  1. Adding a contentType field to V2HttpEndpointExample in the IR
  2. Synthesizing endpoint-level examples from per-body native OAS examples (only for multi-content-type endpoints)
  3. Tagging each synthesized example with its content type

Changes Made

IR schema (packages/ir-sdk/)

  • Added optional contentType: string to V2HttpEndpointExample in examples.yml
  • Bumped IR version to 67.7.0
  • Regenerated TypeScript SDK types

OAS importer (packages/cli/api-importers/openapi-to-ir/)

  • OperationConverter.ts: Added synthesizeEndpointExamplesFromPerContentTypeRequestBodies() — when endpoint-level examples are empty AND there are multiple content types, iterates each ConvertedRequestBody.v2Examples, pairs them with matching response examples, and creates endpoint-level V2HttpEndpointExample entries tagged with contentType
  • Guard condition: convertedRequestBodies.length > 1 — single-content-type endpoints continue using the normal v1→v2 synthesis pipeline

IR utils / other importers

  • generateEndpointExample.ts: Passes through contentType: undefined for non-content-type examples
  • MethodConverter.ts, InitializeEmptyServiceExample.ts: Added contentType field to satisfy type

Test fixtures

  • Added multi-content-type-examples fixture with two content types (application/json, application/ld+json) sharing a schema but with distinct examples including @context/@type fields
  • Updated 42 snapshot files (all changes are the new contentType: undefined field on existing examples)
  • New snapshots verify both content types' examples appear in endpoint-level examples with correct content-type tags

Testing

  • Unit tests added/updated — new multi-content-type-examples test case
  • All existing snapshots verified — only contentType: undefined additions, no example quality degradation
  • pnpm lint:biome --fix && pnpm format:fix && pnpm check:fix all pass
  • pnpm turbo run compile --filter=@fern-api/openapi-to-ir --filter=@fern-api/register passes

Link to Devin session: https://app.devin.ai/sessions/55c94557f22f484f81fcad5894748003
Requested by: @adidavid014


Open in Devin Review

When an OpenAPI operation declares multiple content types (e.g.
application/json and application/ld+json) with distinct per-mediaType
examples, the importer now elevates those body-level examples into
endpoint-level v2Examples tagged with their content type.

Changes:
- Add optional contentType field to V2HttpEndpointExample (IR v67.7.0)
- Implement synthesizeEndpointExamplesFromPerContentTypeRequestBodies()
  in OperationConverter.ts
- Add extractNativeResponseExamplesByContentType() helper to pair
  response examples with the correct content type
- Add test fixture: multi-content-type-examples

This enables the docs UI to display the correct example body when the
user switches content types in the playground/API reference.

Resolves: FER-11417
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@adidavid014 adidavid014 self-assigned this Jun 25, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 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

@devin-ai-integration devin-ai-integration Bot changed the title feat: extract per-content-type OAS examples into endpoint-level v2Examples feat(ir): extract per-content-type OAS examples into endpoint-level v2Examples Jun 25, 2026

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

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.

Devin Review found 1 potential issue.

Open in Devin Review

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.

🚩 Pre-existing counter bug in OpenRPC example conversion causes unnamed examples to overwrite each other

The ++i increment at packages/cli/api-importers/openrpc-to-ir/src/1.x/methods/MethodConverter.ts:360 is placed outside the for loop (lines 275–358) but inside the if block. This means i stays at 0 for every iteration of the loop. When multiple examples lack an explicit name, they all receive the fallback key "Example 1" (line 305: resolvedExample.name ?? \Example ${i + 1}`), and each subsequent unnamed example silently overwrites the previous one in the examplesrecord. Only the last unnamed example survives. This is a pre-existing bug not introduced by this PR (which only addedcontentType: undefined` at line 330), but it's in the same function and worth noting for a follow-up fix.

(Refers to line 360)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Good catch — this is indeed a pre-existing bug (the ++i is outside the loop). This PR only added contentType: undefined at line 330 and doesn't change the iteration logic. I'll note this for a follow-up fix.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-06-25T05:19:47Z).

Fixture main PR Delta
docs 220.4s (n=5) 237.5s (35 versions) +17.1s (+7.8%)

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-06-25T05:19:47Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-06-25 19:28 UTC

adidavid014 and others added 3 commits June 25, 2026 18:31
…pe endpoints only

- Change fallback condition from convertedRequestBodies.length > 0 to > 1
  so single-content-type endpoints keep using normal v1→v2 example synthesis
- Remove debug console.logs from schema-level and response-level tests
- Add multi-content-type-examples test fixture and snapshot
- Update all affected snapshots (contentType field addition)

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

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

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-06-25T05:19:47Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 71s (n=5) 109s (n=5) 64s -7s (-9.9%)
go-sdk square 134s (n=5) 284s (n=5) 131s -3s (-2.2%)
java-sdk square 213s (n=5) 282s (n=5) 208s -5s (-2.3%)
php-sdk square 59s (n=5) 84s (n=5) 53s -6s (-10.2%)
python-sdk square 136s (n=5) 241s (n=5) 131s -5s (-3.7%)
ruby-sdk-v2 square 91s (n=5) 129s (n=5) 85s -6s (-6.6%)
rust-sdk square 183s (n=5) 173s (n=5) 166s -17s (-9.3%)
swift-sdk square 57s (n=5) 431s (n=5) 60s +3s (+5.3%)
ts-sdk square 236s (n=5) 239s (n=5) 229s -7s (-3.0%)

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-06-25T05:19:47Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-06-25 19:29 UTC

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