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); }