Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,26 +116,21 @@ export abstract class AbstractDynamicSnippetsGeneratorContext {
values: FernIr.dynamic.Values;
ignoreMissingParameters?: boolean;
}): TypeInstance[] {
// Iterate `parameters` (IR / SDK signature order) rather than the input `values` object so
// that callers which rely on argument order — notably positional path parameters in
// generated snippets — produce output that matches the SDK signature. Input key order is
// not guaranteed to match the SDK order (e.g. it may be alphabetical from the source spec).
const instances: TypeInstance[] = [];
for (const [key, value] of Object.entries(values)) {
const matchedWireValues = new Set<string>();
for (const parameter of parameters) {
const wireValue = parameter.name.wireValue;
const value = values[wireValue];
if (value === undefined) {
continue;
}
this.errors.scope(key);
matchedWireValues.add(wireValue);
this.errors.scope(wireValue);
try {
const parameter = parameters.find((param) => param.name.wireValue === key);
if (parameter == null) {
if (ignoreMissingParameters) {
// Required for request payloads that include more information than
// just the target parameters (e.g. union base properties).
continue;
}
this.errors.add({
severity: Severity.Critical,
message: this.newParameterNotRecognizedError(key).message
});
continue;
}
instances.push({
name: parameter.name,
typeReference: parameter.typeReference,
Expand All @@ -145,6 +140,22 @@ export abstract class AbstractDynamicSnippetsGeneratorContext {
this.errors.unscope();
}
}
if (!ignoreMissingParameters) {
for (const [key, value] of Object.entries(values)) {
if (value === undefined || matchedWireValues.has(key)) {
continue;
}
this.errors.scope(key);
try {
this.errors.add({
severity: Severity.Critical,
message: this.newParameterNotRecognizedError(key).message
});
} finally {
this.errors.unscope();
}
}
}
return instances;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class DynamicSnippetsTestRunner {
this.runImdbTests(args);
this.runMultiUrlEnvironmentTests(args);
this.runNullableTests(args);
this.runPathParametersTests(args);
this.runReadWriteOnlyTests(args);
this.runRequiredHeadersTests(args);
this.runSingleUrlEnvironmentDefaultTests(args);
Expand Down Expand Up @@ -926,6 +927,43 @@ export class DynamicSnippetsTestRunner {
});
}

private runPathParametersTests(args: DynamicSnippetsTestRunner.Args): void {
const generator = args.buildGenerator({
irFilepath: AbsoluteFilePath.of(join(DYNAMIC_IR_TEST_DEFINITIONS_DIRECTORY, "path-parameters.json"))
});
this.runDynamicSnippetTests({
fixture: "path-parameters",
generator,
testCases: [
{
// Regression test for FER-10546: path-parameter arguments must render in IR
// (SDK signature / URL) order even when the input `pathParameters` object
// supplies them in a different order (here: alphabetical).
description:
"GET /{tenant_id}/user/{user_id}/specifics/{version}/{thought} (alphabetical input order)",
giveRequest: {
endpoint: {
method: "GET",
path: "/{tenant_id}/user/{user_id}/specifics/{version}/{thought}"
},
baseURL: undefined,
environment: undefined,
auth: undefined,
pathParameters: {
tenant_id: "my_tenant",
thought: "my_thought",
user_id: "my_user",
version: 7
},
queryParameters: undefined,
headers: undefined,
requestBody: undefined
}
}
]
});
}

private runDynamicSnippetTests(testSuite: DynamicSnippetsTestRunner.TestSuite) {
describe(testSuite.fixture, () => {
test.each(testSuite.testCases)("$description", async ({ giveRequest }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,10 @@ var client = new AcmeClient(
await client.Service.CreateBigEntityAsync(
new BigEntity {
CastMember = new Actor {
ID = "john.doe",
Name = "John Doe"
Name = "John Doe",
ID = "john.doe"
},
ExtendedMovie = new ExtendedMovie {
Cast = new List<string>(){
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
}
,
ID = "movie-sda231x",
Title = "Pulp Fiction",
From = "Quentin Tarantino",
Expand All @@ -86,7 +79,14 @@ await client.Service.CreateBigEntityAsync(
,
}
,
Revenue = 1000000L
Revenue = 1000000L,
Cast = new List<string>(){
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
}

},
EventInfo = new EventInfo(
new Metadata {
Expand Down Expand Up @@ -562,6 +562,23 @@ await client.Nullable.GetUsersAsync(
"
`;

exports[`snippets (default) > path-parameters > 'GET /{tenant_id}/user/{user_id}/speci…' 1`] = `
"using Acme;
using Acme.User;

var client = new AcmeClient();

await client.User.GetUserSpecificsAsync(
new GetUserSpecificsRequest {
TenantID = "my_tenant",
UserID = "my_user",
Version = 7,
Thought = "my_thought"
}
);
"
`;

exports[`snippets (default) > read-write-only > 'Body properties' 1`] = `
"using Acme;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Dynamic snippets now render path-parameter arguments in IR (URL / SDK signature) order
rather than in the order they happen to appear in the input request, so generated
examples line up with the actual SDK method signature even when the spec lists path
parameters in a different order.
type: fix
10 changes: 10 additions & 0 deletions generators/csharp/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 2.66.4
changelogEntry:
- summary: |
Dynamic snippets now render path-parameter arguments in IR (URL / SDK signature) order
rather than in the order they happen to appear in the input request, so generated
examples line up with the actual SDK method signature even when the spec lists path
parameters in a different order.
type: fix
createdAt: "2026-05-15"
irVersion: 66
- version: 2.66.3
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,11 @@ func do() {
request := &acme.BigEntity{
CastMember: &acme.CastMember{
Actor: &acme.Actor{
ID: "john.doe",
Name: "John Doe",
ID: "john.doe",
},
},
ExtendedMovie: &acme.ExtendedMovie{
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
ID: "movie-sda231x",
Title: "Pulp Fiction",
From: "Quentin Tarantino",
Expand All @@ -121,6 +115,12 @@ func do() {
"releaseDate": "2023-12-08",
},
Revenue: int64(1000000),
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
},
EventInfo: &commons.EventInfo{
Metadata: &commons.Metadata{
Expand Down Expand Up @@ -751,6 +751,32 @@ func do() {
"
`;

exports[`snippets (default) > path-parameters > 'GET /{tenant_id}/user/{user_id}/speci…' 1`] = `
"package example

import (
context "context"

acme "github.com/acme/acme-go"
client "github.com/acme/acme-go/client"
)

func do() {
client := client.NewClient()
request := &acme.GetUserSpecificsRequest{
TenantID: "my_tenant",
UserID: "my_user",
Version: 7,
Thought: "my_thought",
}
client.User.GetUserSpecifics(
context.TODO(),
request,
)
}
"
`;

exports[`snippets (default) > read-write-only > 'Body properties' 1`] = `
"package example

Expand Down Expand Up @@ -1072,17 +1098,11 @@ func do() {
request := &acme.BigEntity{
CastMember: &acme.CastMember{
Actor: &acme.Actor{
ID: "john.doe",
Name: "John Doe",
ID: "john.doe",
},
},
ExtendedMovie: &acme.ExtendedMovie{
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
ID: "movie-sda231x",
Title: "Pulp Fiction",
From: "Quentin Tarantino",
Expand All @@ -1097,6 +1117,12 @@ func do() {
"releaseDate": "2023-12-08",
},
Revenue: int64(1000000),
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
},
EventInfo: &commons.EventInfo{
Metadata: &commons.Metadata{
Expand Down Expand Up @@ -1727,6 +1753,32 @@ func do() {
"
`;

exports[`snippets (exportAllRequestsAtRoot) > path-parameters > 'GET /{tenant_id}/user/{user_id}/speci…' 1`] = `
"package example

import (
context "context"

acme "github.com/acme/acme-go"
client "github.com/acme/acme-go/client"
)

func do() {
client := client.NewClient()
request := &acme.GetUserSpecificsRequest{
TenantID: "my_tenant",
UserID: "my_user",
Version: 7,
Thought: "my_thought",
}
client.User.GetUserSpecifics(
context.TODO(),
request,
)
}
"
`;

exports[`snippets (exportAllRequestsAtRoot) > read-write-only > 'Body properties' 1`] = `
"package example

Expand Down Expand Up @@ -2048,17 +2100,11 @@ func do() {
request := &acme.BigEntity{
CastMember: &acme.CastMember{
Actor: &acme.Actor{
ID: "john.doe",
Name: "John Doe",
ID: "john.doe",
},
},
ExtendedMovie: &acme.ExtendedMovie{
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
ID: "movie-sda231x",
Title: "Pulp Fiction",
From: "Quentin Tarantino",
Expand All @@ -2073,6 +2119,12 @@ func do() {
"releaseDate": "2023-12-08",
},
Revenue: int64(1000000),
Cast: []string{
"John Travolta",
"Samuel L. Jackson",
"Uma Thurman",
"Bruce Willis",
},
},
EventInfo: &commons.EventInfo{
Metadata: &commons.Metadata{
Expand Down Expand Up @@ -2703,6 +2755,32 @@ func do() {
"
`;

exports[`snippets (exportedClientName) > path-parameters > 'GET /{tenant_id}/user/{user_id}/speci…' 1`] = `
"package example

import (
context "context"

acme "github.com/acme/acme-go"
client "github.com/acme/acme-go/client"
)

func do() {
client := client.NewFernClient()
request := &acme.GetUserSpecificsRequest{
TenantID: "my_tenant",
UserID: "my_user",
Version: 7,
Thought: "my_thought",
}
client.User.GetUserSpecifics(
context.TODO(),
request,
)
}
"
`;

exports[`snippets (exportedClientName) > read-write-only > 'Body properties' 1`] = `
"package example

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json

- summary: |
Dynamic snippets now render path-parameter arguments in IR (URL / SDK signature) order
rather than in the order they happen to appear in the input request, so generated
examples line up with the actual SDK method signature even when the spec lists path
parameters in a different order.
type: fix
Loading
Loading