From d79f3a629b47f076a33e6141f5bd78e26976a75c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:20:25 +0000
Subject: [PATCH 1/8] Initial plan
From 770dd54a4802989f21e21fafd3101d7103ef6263 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:27:51 +0000
Subject: [PATCH 2/8] Guard exploded string-struct query params against null
ToString in C# emitter
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 33 +++++++++++++++++--
.../RestClientProviderTests.cs | 6 ++++
...dCreateRequestMethodWithQueryParameters.cs | 22 ++++++++++++-
3 files changed, 58 insertions(+), 3 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index 5cee8c429b3..7cdde105fed 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -639,7 +639,18 @@ private static MethodBodyStatement BuildAppendQueryStatement(
var convertedItem = paramType.ElementType.IsEnum
? paramType.ElementType.ToSerial(item)
: item.Value;
- forEachStatement.Add(uri.AppendQuery(item.Key, convertedItem, true).Terminate());
+ MethodBodyStatement appendItemStatement = uri.AppendQuery(item.Key, convertedItem, true).Terminate();
+ // A string-backed extensible enum serializes via `ToString()`, which returns null for a
+ // default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
+ // `Uri.EscapeDataString`, so guard each value against null/empty before appending.
+ if (IsExtensibleStringEnum(paramType.ElementType))
+ {
+ appendItemStatement = new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), convertedItem)))
+ {
+ appendItemStatement
+ };
+ }
+ forEachStatement.Add(appendItemStatement);
return forEachStatement;
}
else
@@ -674,11 +685,29 @@ private static MethodBodyStatement BuildAppendQueryStatement(
{
convertedItem = item;
}
- forEachStatement.Add(uri.AppendQuery(Literal(inputQueryParameter.SerializedName), convertedItem, true).Terminate());
+ MethodBodyStatement appendItemStatement = uri.AppendQuery(Literal(inputQueryParameter.SerializedName), convertedItem, true).Terminate();
+ // A string-backed extensible enum serializes via `ToString()`, which returns null for a
+ // default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
+ // `Uri.EscapeDataString`, so guard each element against null/empty before appending.
+ if (IsExtensibleStringEnum(paramType.ElementType))
+ {
+ appendItemStatement = new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), convertedItem)))
+ {
+ appendItemStatement
+ };
+ }
+ forEachStatement.Add(appendItemStatement);
return forEachStatement;
}
}
+ ///
+ /// Returns true when is a string-backed extensible enum, which is
+ /// generated as a struct whose ToString() returns the internal (possibly null) string value.
+ ///
+ private static bool IsExtensibleStringEnum(CSharpType type)
+ => type.IsEnum && type.IsStruct && type.UnderlyingEnumType == typeof(string);
+
///
/// Builds the statements for a model-typed query parameter that uses form-style `explode`.
/// Each (simple) property of the model is emitted as its own query entry using the property's
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
index 07de6b5411a..2bf3b8a7e16 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
@@ -1054,6 +1054,10 @@ public void TestBuildCreateRequestMethodWithQueryParameters()
var inputStringEnum = InputFactory.StringEnum(
"foo",
stringEnumValues);
+ var inputExtensibleStringEnum = InputFactory.StringEnum(
+ "extensibleFoo",
+ stringEnumValues,
+ isExtensible: true);
var inputIntEnum = InputFactory.Int32Enum(
"intFoo",
intEnumValues);
@@ -1078,6 +1082,8 @@ public void TestBuildCreateRequestMethodWithQueryParameters()
InputFactory.QueryParameter("p7Explode", InputFactory.Dictionary(inputIntEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p8Explode", InputFactory.Array(inputFloatEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p9Explode", InputFactory.Array(inputDoubleEnum), isRequired: true, explode: true),
+ InputFactory.QueryParameter("p10Explode", InputFactory.Array(inputExtensibleStringEnum), isRequired: true, explode: true),
+ InputFactory.QueryParameter("p11Explode", InputFactory.Dictionary(inputExtensibleStringEnum), isRequired: true, explode: true),
];
var operation = InputFactory.Operation(
"sampleOp",
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index 60b5383a969..34b231eb932 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -10,7 +10,7 @@ namespace Sample
{
public partial class TestClient
{
- internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, string optionalParam, global::System.ClientModel.Primitives.RequestOptions options)
+ internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, global::System.Collections.Generic.IEnumerable p10Explode, global::System.Collections.Generic.IDictionary p11Explode, string optionalParam, global::System.ClientModel.Primitives.RequestOptions options)
{
global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder();
uri.Reset(_endpoint);
@@ -99,6 +99,26 @@ public partial class TestClient
uri.AppendQuery("p9Explode", ((double)@param), true);
}
}
+ if (((p10Explode != null) && !((p10Explode is global::Sample.ChangeTrackingList changeTrackingList7) && changeTrackingList7.IsUndefined)))
+ {
+ foreach (var @param in p10Explode)
+ {
+ if (!string.IsNullOrEmpty(@param.ToString()))
+ {
+ uri.AppendQuery("p10Explode", @param.ToString(), true);
+ }
+ }
+ }
+ if (((p11Explode != null) && !((p11Explode is global::Sample.ChangeTrackingDictionary changeTrackingDictionary3) && changeTrackingDictionary3.IsUndefined)))
+ {
+ foreach (var @param in p11Explode)
+ {
+ if (!string.IsNullOrEmpty(@param.ToString()))
+ {
+ uri.AppendQuery(@param.Key, @param.ToString(), true);
+ }
+ }
+ }
global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200);
global::System.ClientModel.Primitives.PipelineRequest request = message.Request;
message.Apply(options);
From 764fbcfc678c7795bbb490eeb182090d5bcb98c4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:30:38 +0000
Subject: [PATCH 3/8] Fix dictionary explode enum serialization to use
KeyValuePair value
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 2 +-
.../TestBuildCreateRequestMethodWithQueryParameters.cs | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index 7cdde105fed..c321f023e23 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -637,7 +637,7 @@ private static MethodBodyStatement BuildAppendQueryStatement(
valueExpression.AsDictionary(paramType),
out KeyValuePairExpression item);
var convertedItem = paramType.ElementType.IsEnum
- ? paramType.ElementType.ToSerial(item)
+ ? paramType.ElementType.ToSerial(item.Value)
: item.Value;
MethodBodyStatement appendItemStatement = uri.AppendQuery(item.Key, convertedItem, true).Terminate();
// A string-backed extensible enum serializes via `ToString()`, which returns null for a
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index 34b231eb932..fc52098ea9d 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -75,14 +75,14 @@ public partial class TestClient
{
foreach (var @param in p6Explode)
{
- uri.AppendQuery(@param.Key, @param.ToSerialString(), true);
+ uri.AppendQuery(@param.Key, @param.Value.ToSerialString(), true);
}
}
if (((p7Explode != null) && !((p7Explode is global::Sample.ChangeTrackingDictionary changeTrackingDictionary2) && changeTrackingDictionary2.IsUndefined)))
{
foreach (var @param in p7Explode)
{
- uri.AppendQuery(@param.Key, ((int)@param), true);
+ uri.AppendQuery(@param.Key, ((int)@param.Value), true);
}
}
if (((p8Explode != null) && !((p8Explode is global::Sample.ChangeTrackingList changeTrackingList5) && changeTrackingList5.IsUndefined)))
@@ -113,9 +113,9 @@ public partial class TestClient
{
foreach (var @param in p11Explode)
{
- if (!string.IsNullOrEmpty(@param.ToString()))
+ if (!string.IsNullOrEmpty(@param.Value.ToString()))
{
- uri.AppendQuery(@param.Key, @param.ToString(), true);
+ uri.AppendQuery(@param.Key, @param.Value.ToString(), true);
}
}
}
From 5f1e2277985f443d002b3233ce97d71a8a1d54b2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 16:30:55 +0000
Subject: [PATCH 4/8] Cache ToString() result in local variable for extensible
string enum explode query params
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 32 ++++++++++++-------
...dCreateRequestMethodWithQueryParameters.cs | 10 +++---
2 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index c321f023e23..db8206b68d7 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -639,18 +639,22 @@ private static MethodBodyStatement BuildAppendQueryStatement(
var convertedItem = paramType.ElementType.IsEnum
? paramType.ElementType.ToSerial(item.Value)
: item.Value;
- MethodBodyStatement appendItemStatement = uri.AppendQuery(item.Key, convertedItem, true).Terminate();
// A string-backed extensible enum serializes via `ToString()`, which returns null for a
// default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
- // `Uri.EscapeDataString`, so guard each value against null/empty before appending.
+ // `Uri.EscapeDataString`, so cache the serialized value and guard against null/empty before
+ // appending.
if (IsExtensibleStringEnum(paramType.ElementType))
{
- appendItemStatement = new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), convertedItem)))
+ forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
+ forEachStatement.Add(new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), cachedVar)))
{
- appendItemStatement
- };
+ uri.AppendQuery(item.Key, cachedVar, true).Terminate()
+ });
+ }
+ else
+ {
+ forEachStatement.Add(uri.AppendQuery(item.Key, convertedItem, true).Terminate());
}
- forEachStatement.Add(appendItemStatement);
return forEachStatement;
}
else
@@ -685,18 +689,22 @@ private static MethodBodyStatement BuildAppendQueryStatement(
{
convertedItem = item;
}
- MethodBodyStatement appendItemStatement = uri.AppendQuery(Literal(inputQueryParameter.SerializedName), convertedItem, true).Terminate();
// A string-backed extensible enum serializes via `ToString()`, which returns null for a
// default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
- // `Uri.EscapeDataString`, so guard each element against null/empty before appending.
+ // `Uri.EscapeDataString`, so cache the serialized value and guard against null/empty before
+ // appending.
if (IsExtensibleStringEnum(paramType.ElementType))
{
- appendItemStatement = new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), convertedItem)))
+ forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
+ forEachStatement.Add(new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), cachedVar)))
{
- appendItemStatement
- };
+ uri.AppendQuery(Literal(inputQueryParameter.SerializedName), cachedVar, true).Terminate()
+ });
+ }
+ else
+ {
+ forEachStatement.Add(uri.AppendQuery(Literal(inputQueryParameter.SerializedName), convertedItem, true).Terminate());
}
- forEachStatement.Add(appendItemStatement);
return forEachStatement;
}
}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index fc52098ea9d..5e7ea8d589e 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -103,9 +103,10 @@ public partial class TestClient
{
foreach (var @param in p10Explode)
{
- if (!string.IsNullOrEmpty(@param.ToString()))
+ string paramStr = @param.ToString();
+ if (!string.IsNullOrEmpty(paramStr))
{
- uri.AppendQuery("p10Explode", @param.ToString(), true);
+ uri.AppendQuery("p10Explode", paramStr, true);
}
}
}
@@ -113,9 +114,10 @@ public partial class TestClient
{
foreach (var @param in p11Explode)
{
- if (!string.IsNullOrEmpty(@param.Value.ToString()))
+ string paramStr = @param.Value.ToString();
+ if (!string.IsNullOrEmpty(paramStr))
{
- uri.AppendQuery(@param.Key, @param.Value.ToString(), true);
+ uri.AppendQuery(@param.Key, paramStr, true);
}
}
}
From 51e61a38db000980dda717d7cae1bc3022b473fd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 16:40:26 +0000
Subject: [PATCH 5/8] Refactor explode query param codegen: use
StringSnippets.IsNullOrEmpty and shared helper
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 55 ++++++++-----------
1 file changed, 23 insertions(+), 32 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index db8206b68d7..43eb5e1ad49 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -639,22 +639,7 @@ private static MethodBodyStatement BuildAppendQueryStatement(
var convertedItem = paramType.ElementType.IsEnum
? paramType.ElementType.ToSerial(item.Value)
: item.Value;
- // A string-backed extensible enum serializes via `ToString()`, which returns null for a
- // default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
- // `Uri.EscapeDataString`, so cache the serialized value and guard against null/empty before
- // appending.
- if (IsExtensibleStringEnum(paramType.ElementType))
- {
- forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
- forEachStatement.Add(new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), cachedVar)))
- {
- uri.AppendQuery(item.Key, cachedVar, true).Terminate()
- });
- }
- else
- {
- forEachStatement.Add(uri.AppendQuery(item.Key, convertedItem, true).Terminate());
- }
+ AddExplodeQueryItem(forEachStatement, uri, item.Key, convertedItem, paramType.ElementType);
return forEachStatement;
}
else
@@ -689,22 +674,7 @@ private static MethodBodyStatement BuildAppendQueryStatement(
{
convertedItem = item;
}
- // A string-backed extensible enum serializes via `ToString()`, which returns null for a
- // default-constructed value. Passing null to `AppendQuery(..., escape: true)` throws in
- // `Uri.EscapeDataString`, so cache the serialized value and guard against null/empty before
- // appending.
- if (IsExtensibleStringEnum(paramType.ElementType))
- {
- forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
- forEachStatement.Add(new IfStatement(Not(Static(typeof(string)).Invoke(nameof(string.IsNullOrEmpty), cachedVar)))
- {
- uri.AppendQuery(Literal(inputQueryParameter.SerializedName), cachedVar, true).Terminate()
- });
- }
- else
- {
- forEachStatement.Add(uri.AppendQuery(Literal(inputQueryParameter.SerializedName), convertedItem, true).Terminate());
- }
+ AddExplodeQueryItem(forEachStatement, uri, Literal(inputQueryParameter.SerializedName), convertedItem, paramType.ElementType);
return forEachStatement;
}
}
@@ -716,6 +686,27 @@ private static MethodBodyStatement BuildAppendQueryStatement(
private static bool IsExtensibleStringEnum(CSharpType type)
=> type.IsEnum && type.IsStruct && type.UnderlyingEnumType == typeof(string);
+ private static void AddExplodeQueryItem(
+ ForEachStatement forEachStatement,
+ ScopedApi uri,
+ ValueExpression key,
+ ValueExpression convertedItem,
+ CSharpType elementType)
+ {
+ if (IsExtensibleStringEnum(elementType))
+ {
+ forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
+ forEachStatement.Add(new IfStatement(Not(StringSnippets.IsNullOrEmpty(cachedVar.As())))
+ {
+ uri.AppendQuery(key, cachedVar, true).Terminate()
+ });
+ }
+ else
+ {
+ forEachStatement.Add(uri.AppendQuery(key, convertedItem, true).Terminate());
+ }
+ }
+
///
/// Builds the statements for a model-typed query parameter that uses form-style `explode`.
/// Each (simple) property of the model is emitted as its own query entry using the property's
From dd8b3edc168b5c70e689b3e584d75411c647f6db Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 17:04:05 +0000
Subject: [PATCH 6/8] fix: remove doc comment on IsExtensibleStringEnum, add
scalar extensible enum query param test
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 4 ----
.../Providers/RestClientProviders/RestClientProviderTests.cs | 1 +
.../TestBuildCreateRequestMethodWithQueryParameters.cs | 3 ++-
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index 43eb5e1ad49..4c708c53d61 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -679,10 +679,6 @@ private static MethodBodyStatement BuildAppendQueryStatement(
}
}
- ///
- /// Returns true when is a string-backed extensible enum, which is
- /// generated as a struct whose ToString() returns the internal (possibly null) string value.
- ///
private static bool IsExtensibleStringEnum(CSharpType type)
=> type.IsEnum && type.IsStruct && type.UnderlyingEnumType == typeof(string);
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
index 2bf3b8a7e16..164d6970c04 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
@@ -1084,6 +1084,7 @@ public void TestBuildCreateRequestMethodWithQueryParameters()
InputFactory.QueryParameter("p9Explode", InputFactory.Array(inputDoubleEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p10Explode", InputFactory.Array(inputExtensibleStringEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p11Explode", InputFactory.Dictionary(inputExtensibleStringEnum), isRequired: true, explode: true),
+ InputFactory.QueryParameter("p12", inputExtensibleStringEnum, isRequired: true),
];
var operation = InputFactory.Operation(
"sampleOp",
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index 5e7ea8d589e..36a240f2403 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -10,7 +10,7 @@ namespace Sample
{
public partial class TestClient
{
- internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, global::System.Collections.Generic.IEnumerable p10Explode, global::System.Collections.Generic.IDictionary p11Explode, string optionalParam, global::System.ClientModel.Primitives.RequestOptions options)
+ internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, global::System.Collections.Generic.IEnumerable p10Explode, global::System.Collections.Generic.IDictionary p11Explode, string p12, string optionalParam, global::System.ClientModel.Primitives.RequestOptions options)
{
global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder();
uri.Reset(_endpoint);
@@ -121,6 +121,7 @@ public partial class TestClient
}
}
}
+ uri.AppendQuery("p12", p12, true);
global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200);
global::System.ClientModel.Primitives.PipelineRequest request = message.Request;
message.Apply(options);
From f473482ace38f425b6d84255494252142fb2d0d9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 18:45:21 +0000
Subject: [PATCH 7/8] Add nullable optional extensible string enum test case
(p13) with null guard validation
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../RestClientProviders/RestClientProviderTests.cs | 1 +
.../TestBuildCreateRequestMethodWithQueryParameters.cs | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
index 164d6970c04..1239e640469 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs
@@ -1085,6 +1085,7 @@ public void TestBuildCreateRequestMethodWithQueryParameters()
InputFactory.QueryParameter("p10Explode", InputFactory.Array(inputExtensibleStringEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p11Explode", InputFactory.Dictionary(inputExtensibleStringEnum), isRequired: true, explode: true),
InputFactory.QueryParameter("p12", inputExtensibleStringEnum, isRequired: true),
+ InputFactory.QueryParameter("p13", new InputNullableType(inputExtensibleStringEnum), isRequired: false),
];
var operation = InputFactory.Operation(
"sampleOp",
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index 36a240f2403..0466a71317e 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -10,7 +10,7 @@ namespace Sample
{
public partial class TestClient
{
- internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, global::System.Collections.Generic.IEnumerable p10Explode, global::System.Collections.Generic.IDictionary p11Explode, string p12, string optionalParam, global::System.ClientModel.Primitives.RequestOptions options)
+ internal global::System.ClientModel.Primitives.PipelineMessage CreateSampleOpRequest(global::System.Collections.Generic.IEnumerable p1Explode, global::System.Collections.Generic.IEnumerable p1, global::System.Collections.Generic.IEnumerable p2Explode, global::System.Collections.Generic.IEnumerable p2, global::System.Collections.Generic.IDictionary p3Explode, global::System.Collections.Generic.IDictionary p3, global::System.Collections.Generic.IEnumerable p4Explode, global::System.Collections.Generic.IEnumerable p5Explode, global::System.Collections.Generic.IDictionary p6Explode, global::System.Collections.Generic.IDictionary p7Explode, global::System.Collections.Generic.IEnumerable p8Explode, global::System.Collections.Generic.IEnumerable p9Explode, global::System.Collections.Generic.IEnumerable p10Explode, global::System.Collections.Generic.IDictionary p11Explode, string p12, string optionalParam, string p13, global::System.ClientModel.Primitives.RequestOptions options)
{
global::Sample.ClientUriBuilder uri = new global::Sample.ClientUriBuilder();
uri.Reset(_endpoint);
@@ -122,6 +122,10 @@ public partial class TestClient
}
}
uri.AppendQuery("p12", p12, true);
+ if ((p13 != null))
+ {
+ uri.AppendQuery("p13", p13, true);
+ }
global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200);
global::System.ClientModel.Primitives.PipelineRequest request = message.Request;
message.Apply(options);
From 511a71e1400f8ef92cb6365cb774459f967571c0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Jul 2026 15:02:09 +0000
Subject: [PATCH 8/8] fix: change null guard from IsNullOrEmpty to null-only
check for extensible string enum explode
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
---
.../src/Providers/RestClientProvider.cs | 2 +-
.../TestBuildCreateRequestMethodWithQueryParameters.cs | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
index 4c708c53d61..0c442c416bc 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs
@@ -692,7 +692,7 @@ private static void AddExplodeQueryItem(
if (IsExtensibleStringEnum(elementType))
{
forEachStatement.Add(Declare("paramStr", typeof(string), convertedItem, out VariableExpression cachedVar));
- forEachStatement.Add(new IfStatement(Not(StringSnippets.IsNullOrEmpty(cachedVar.As())))
+ forEachStatement.Add(new IfStatement(cachedVar.As().NotEqual(Null))
{
uri.AppendQuery(key, cachedVar, true).Terminate()
});
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
index 0466a71317e..ed43cefed82 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/TestBuildCreateRequestMethodWithQueryParameters.cs
@@ -104,7 +104,7 @@ public partial class TestClient
foreach (var @param in p10Explode)
{
string paramStr = @param.ToString();
- if (!string.IsNullOrEmpty(paramStr))
+ if ((paramStr != null))
{
uri.AppendQuery("p10Explode", paramStr, true);
}
@@ -115,7 +115,7 @@ public partial class TestClient
foreach (var @param in p11Explode)
{
string paramStr = @param.Value.ToString();
- if (!string.IsNullOrEmpty(paramStr))
+ if ((paramStr != null))
{
uri.AppendQuery(@param.Key, paramStr, true);
}