Describe the bug
When a TypeSpec model property is a string-backed struct (e.g. an extensible enum backed by a string) and it is used as an exploded @query array parameter, the C# emitter generates code that calls .ToString() on each element and passes the result directly to AppendQuery(..., escape: true).
Because a default-constructed string-backed struct has a null internal string, .ToString() can return null. Uri.EscapeDataString (called internally by AppendQuery) throws an ArgumentNullException when passed a null string, causing a runtime crash.
Example of the generated code pattern that is unsafe:
foreach (var @param in include)
{
uri.AppendQuery("include[]", @param.ToString(), true);
}
The emitter should guard against null/empty values before appending each exploded query parameter, for example:
foreach (var @param in include)
{
if (!string.IsNullOrEmpty(@param.ToString()))
{
uri.AppendQuery("include[]", @param.ToString(), true);
}
}
This was identified in openai/openai-dotnet#1255 where IncludedConversationItemProperty is a string-backed struct and the generated ConversationClient.RestClient.cs contained this pattern in multiple locations (lines 43, 62, and 103 of the generated file).
Reproduction
- Define a TypeSpec model with a string-backed extensible enum, e.g.:
@doc("An extensible string enum")
union MyStringEnum {
string,
valueA: "value-a",
valueB: "value-b",
}
- Use it as an exploded array query parameter on an operation:
op myOp(
@query(#{ explode: true }) include: MyStringEnum[],
): void;
- Emit the C# client with
@typespec/http-client-csharp.
- Observe the generated code calls
@param.ToString() without a null guard inside the foreach loop before passing to AppendQuery(..., escape: true).
- At runtime, constructing a default
MyStringEnum value (null internal string) and including it in the include list causes Uri.EscapeDataString to throw ArgumentNullException.
Checklist
Describe the bug
When a TypeSpec model property is a string-backed struct (e.g. an extensible enum backed by a
string) and it is used as an exploded@queryarray parameter, the C# emitter generates code that calls.ToString()on each element and passes the result directly toAppendQuery(..., escape: true).Because a default-constructed string-backed struct has a
nullinternal string,.ToString()can returnnull.Uri.EscapeDataString(called internally byAppendQuery) throws anArgumentNullExceptionwhen passed anullstring, causing a runtime crash.Example of the generated code pattern that is unsafe:
The emitter should guard against null/empty values before appending each exploded query parameter, for example:
This was identified in openai/openai-dotnet#1255 where
IncludedConversationItemPropertyis a string-backed struct and the generatedConversationClient.RestClient.cscontained this pattern in multiple locations (lines 43, 62, and 103 of the generated file).Reproduction
@typespec/http-client-csharp.@param.ToString()without a null guard inside theforeachloop before passing toAppendQuery(..., escape: true).MyStringEnumvalue (null internal string) and including it in theincludelist causesUri.EscapeDataStringto throwArgumentNullException.Checklist