Skip to content

fix(csharp): skip mock test examples with empty path parameters#14879

Merged
Swimburger merged 3 commits intomainfrom
devin/1775797276-skip-empty-path-params
Apr 10, 2026
Merged

fix(csharp): skip mock test examples with empty path parameters#14879
Swimburger merged 3 commits intomainfrom
devin/1775797276-skip-empty-path-params

Conversation

@Swimburger
Copy link
Copy Markdown
Member

@Swimburger Swimburger commented Apr 10, 2026

Description

Refs #14875

Bundles several C# generator fixes into version 2.59.5:

  1. Skip mock test examples with empty path parameters — When an OpenAPI spec provides an x-fern-examples entry with an empty string path parameter (e.g., "id": ""), WireMock collapses the path while the SDK preserves the double-slash, causing a 404. These examples are now filtered out.

  2. Fix dynamic snippet generation for literal types with generate-literals enabled — Inline literal properties now emit a nop (relying on the = new() default initializer in the C# model), and named literal alias types emit new TypeName() instead of a raw string, preventing CS0029 compilation errors.

  3. Fix list<file> upload propertiesfileArray properties were incorrectly generating a single FileParameter field instead of List<FileParameter>.

  4. Add precision fallbacks to JsonElementComparer — Handles float/double values where the decimal representation differs from the original JSON string.

Changes Made

  • CsharpTypeMapper.ts: Wrap fileArray case in Collection.list()
  • DynamicLiteralMapper.ts: Return nop() for inline literals when generateLiterals is on; emit new TypeName() for named literal aliases
  • DynamicTypeMapper.ts: Return class reference for named literal alias types when generateLiterals is on
  • versions.yml: Bump to 2.59.5 with changelog entries
  • seed.yml: Remove several fixtures from allowedFailures (now passing)
  • Updated seed snapshots across multiple fixtures

Testing

  • Seed tests pass for affected fixtures
  • Validated against Hume C# SDK (0 test failures)
  • Lint passes

Link to Devin session: https://app.devin.ai/sessions/1dcf1274fbe74a7cbac9f76e9a65ed28
Requested by: @Swimburger

When an endpoint example has an empty string path parameter, the mock
server URL collapses the empty segment (e.g. /v0/tools/version/1) while
the SDK client sends a double-slash URL (e.g. /v0/tools//version/1),
causing WireMock to return 404. Filter out such examples from mock
server test generation.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@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 and CI monitoring

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copy link
Copy Markdown
Contributor

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

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 potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@github-actions
Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against latest nightly baseline on main (2026-04-10T04:56:48Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 100s 135s 91s -9s (-9.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-04-10T04:56:48Z). Trigger benchmark-baseline to refresh.

@Swimburger Swimburger enabled auto-merge (squash) April 10, 2026 14:14
… to 2.59.5

- Fix dynamic snippet generation for literal types when generate-literals is
  enabled: inline literals emit nop (relying on default initializer), named
  literal aliases emit `new TypeName()` instead of raw strings
- Fix `list<file>` upload properties generating single FileParameter instead
  of List<FileParameter>
- Move empty-path-parameter skip fix to new 2.59.5 release
- Remove several fixtures from allowedFailures (now passing)
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +145 to +147
if (this.settings.generateLiterals) {
return this.csharp.Literal.nop();
}
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.

🔴 convertLiteral blanket nop() return drops literal path parameter arguments, causing compilation errors

When generateLiterals is enabled, convertLiteral returns nop() unconditionally (line 145-146). This is correct for inline object properties (where Class_ literal filters out nop fields at Literal.ts:115), but breaks path parameters that are passed as standalone method arguments. The MethodInvocation.write() method (MethodInvocation.ts:95-106) does NOT filter out nop arguments — it just calls arg.write(writer) which writes nothing. This causes the literal path parameter value to be silently dropped from the method call.

The generated snippet for the literal/readonly-constants fixture confirms this: await client.Path.SendAsync() is missing the required string id argument (see PathClient.cs:83-86 which requires string id). The fallbackToDefault parameter set at EndpointSnippetGenerator.ts:860 is never reached because the early return on line 146 fires first.

Affected code flow
  1. getPathParameters (EndpointSnippetGenerator.ts:855-862) calls convert() on each path param
  2. For literal<"123">, convert() dispatches to convertLiteral()
  3. convertLiteral() returns nop() at line 146 before any value logic
  4. The nop is used as a method argument at EndpointSnippetGenerator.ts:779 or :584
  5. MethodInvocation renders the nop as empty output
  6. Result: SendAsync() instead of SendAsync("123") — CS7036 compilation error
Prompt for agents
The convertLiteral method in DynamicLiteralMapper.ts has a blanket early return that returns nop() when generateLiterals is true. This is too aggressive — it works for inline object properties (which are filtered by Class_ literal's nop filtering in Literal.ts:115), but breaks literal values used as standalone method arguments (path parameters, body request arguments), because MethodInvocation does not filter out nop arguments.

The fix needs to preserve the nop behavior for inline properties while still emitting literal values when they're needed as method arguments. Possible approaches:

1. Remove the early return from convertLiteral and instead handle literal suppression at the object-property level. The Class_ literal already filters nop fields, so you could keep returning the actual literal value and add a separate check in convertObject or at the ConstructorField level to skip literal-typed properties when generateLiterals is on.

2. Add a context flag (e.g., suppressLiterals: boolean) to the DynamicLiteralMapper.Args interface, defaulting to false. Only callers that generate inline properties (convertObject, getInlinedRequestBodyPropertyConstructorFields, header/query fields) would pass suppressLiterals: true. Path parameter callers in EndpointSnippetGenerator.getPathParameters would not set this flag.

3. In EndpointSnippetGenerator.getPathParameters, special-case literal type references: when generateLiterals is true and the parameter typeReference.type is 'literal', emit the literal value directly instead of going through convert().

The affected callers are in EndpointSnippetGenerator.ts: getPathParameters (line 843), getMethodArgsForBodyRequest (line 767), and getMethodArgsForInlinedRequest (line 556). Files: generators/csharp/dynamic-snippets/src/context/DynamicLiteralMapper.ts and generators/csharp/dynamic-snippets/src/EndpointSnippetGenerator.ts.
Open in Devin Review

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

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

@github-actions
Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against latest nightly baseline on main (2026-04-10T04:56:48Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 100s 135s 99s -1s (-1.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-04-10T04:56:48Z). Trigger benchmark-baseline to refresh.

@github-actions
Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against latest nightly baseline on main (2026-04-10T04:56:48Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 100s 135s 91s -9s (-9.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-04-10T04:56:48Z). Trigger benchmark-baseline to refresh.

@Swimburger Swimburger merged commit fc20f80 into main Apr 10, 2026
92 of 93 checks passed
@Swimburger Swimburger deleted the devin/1775797276-skip-empty-path-params branch April 10, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants