Skip to content

Commit f8bb51a

Browse files
authored
Fix optional parameter ordering in generated TS/CS clients (#5135)
1 parent 62ab9f3 commit f8bb51a

File tree

4 files changed

+150
-9
lines changed

4 files changed

+150
-9
lines changed

src/NSwag.CodeGeneration.CSharp.Tests/OptionalParameterTests.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public enum MyEnum
3737
Two,
3838
Three,
3939
Four
40-
4140
}
41+
4242
public class MyClass
4343
{
4444
#pragma warning disable IDE0051
@@ -145,5 +145,66 @@ public async Task When_setting_is_enabled_then_parameters_are_reordered()
145145
// Assert
146146
Assert.Contains("TestAsync(string a, string b, string c = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))", code);
147147
}
148+
149+
[Fact]
150+
public async Task When_optional_parameter_comes_before_required()
151+
{
152+
// Arrange
153+
const string specification = """
154+
{
155+
"openapi": "3.0.0",
156+
"paths": {
157+
"/": {
158+
"get": {
159+
"operationId": "Get",
160+
"parameters": [
161+
{
162+
"name": "firstname",
163+
"in": "query",
164+
"schema": {
165+
"type": "string",
166+
"nullable": true
167+
},
168+
"x-position": 1
169+
},
170+
{
171+
"name": "lastname",
172+
"in": "query",
173+
"required": true,
174+
"schema": {
175+
"type": "string"
176+
},
177+
"x-position": 2
178+
}
179+
],
180+
"responses": {
181+
"200": {
182+
"description": "",
183+
"content": {
184+
"application/json": {
185+
"schema": {
186+
"type": "string"
187+
}
188+
}
189+
}
190+
}
191+
}
192+
}
193+
}
194+
}
195+
}
196+
""";
197+
198+
// Act
199+
var document = await OpenApiDocument.FromJsonAsync(specification, "", SchemaType.OpenApi3);
200+
var generator = new CSharpClientGenerator(document, new CSharpClientGeneratorSettings
201+
{
202+
GenerateOptionalParameters = true
203+
});
204+
var code = generator.GenerateFile();
205+
206+
// Assert
207+
Assert.Contains("GetAsync(string lastname, string firstname = null,", code);
208+
}
148209
}
149210
}

src/NSwag.CodeGeneration.CSharp/Models/CSharpOperationModel.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,22 @@ public CSharpOperationModel(
5555
// TODO: Move to CSharpControllerOperationModel
5656
if (generator is CSharpControllerGenerator)
5757
{
58-
parameters = [.. parameters
59-
.OrderBy(p => p.Position ?? 0)
60-
.ThenBy(p => !p.IsRequired)
61-
.ThenBy(p => p.Default == null)];
58+
parameters =
59+
[
60+
.. parameters
61+
.OrderBy(p => !p.IsRequired)
62+
.ThenBy(p => p.Default == null)
63+
.ThenBy(p => p.Position ?? 0)
64+
];
6265
}
6366
else
6467
{
65-
parameters = [.. parameters
66-
.OrderBy(p => p.Position ?? 0)
67-
.ThenBy(p => !p.IsRequired)];
68+
parameters =
69+
[
70+
.. parameters
71+
.OrderBy(p => !p.IsRequired)
72+
.ThenBy(p => p.Position ?? 0)
73+
];
6874
}
6975
}
7076

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using NJsonSchema;
2+
using Xunit;
3+
4+
namespace NSwag.CodeGeneration.TypeScript.Tests
5+
{
6+
public class OptionalParameterTests
7+
{
8+
[Fact]
9+
public async Task When_optional_parameter_comes_before_required()
10+
{
11+
// Arrange
12+
const string specification = """
13+
{
14+
"openapi": "3.0.0",
15+
"paths": {
16+
"/": {
17+
"get": {
18+
"operationId": "Get",
19+
"parameters": [
20+
{
21+
"name": "firstname",
22+
"in": "query",
23+
"schema": {
24+
"type": "string",
25+
"nullable": true
26+
},
27+
"x-position": 1
28+
},
29+
{
30+
"name": "lastname",
31+
"in": "query",
32+
"required": true,
33+
"schema": {
34+
"type": "string"
35+
},
36+
"x-position": 2
37+
}
38+
],
39+
"responses": {
40+
"200": {
41+
"description": "",
42+
"content": {
43+
"application/json": {
44+
"schema": {
45+
"type": "string"
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
""";
56+
57+
// Act
58+
var document = await OpenApiDocument.FromJsonAsync(specification, "", SchemaType.OpenApi3);
59+
var generator = new TypeScriptClientGenerator(document, new TypeScriptClientGeneratorSettings
60+
{
61+
GenerateOptionalParameters = true
62+
});
63+
var code = generator.GenerateFile();
64+
65+
// Assert
66+
Assert.Contains("get(lastname: string, firstname?: string | null | undefined)", code);
67+
}
68+
}
69+
}

src/NSwag.CodeGeneration.TypeScript/Models/TypeScriptOperationModel.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ public TypeScriptOperationModel(
3939

4040
if (settings.GenerateOptionalParameters)
4141
{
42-
parameters = [.. parameters.OrderBy(p => p.Position ?? 0).ThenBy(p => !p.IsRequired)];
42+
parameters =
43+
[
44+
.. parameters
45+
.OrderBy(p => !p.IsRequired)
46+
.ThenBy(p => p.Position ?? 0)
47+
];
4348
}
4449

4550
Parameters = parameters

0 commit comments

Comments
 (0)