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
26 changes: 17 additions & 9 deletions src/SwaggerProvider.Runtime/RuntimeHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,19 @@ module RuntimeHelpers =
match x with
| Some xs -> [ name, (client.Serialize xs).Trim('\"') ]
| None -> []
// Handle enum arrays before the concrete int32/int64/bool array arms.
// CLR array variance lets any int32-backed enum[] match "| :? array<int32>", which
// would lose the wire-name information for string enums. We intercept enum arrays
// first and serialise each element via the cached buildEnumSerializer.
| :? Array as xs when (let et = xs.GetType().GetElementType() in not(isNull et) && et.IsEnum) ->
let elTy = xs.GetType().GetElementType()
let serializer = enumSerializerCache.GetOrAdd(elTy, enumSerializerFactory)

[ for i in 0 .. xs.Length - 1 do
let param = serializer(xs.GetValue(i))

if not(isNull param) then
yield name, param ]
Comment thread
sergey-tihon marked this conversation as resolved.
| :? array<bool> as xs -> xs |> toStrArray name
| :? array<int32> as xs -> xs |> toStrArray name
| :? array<int64> as xs -> xs |> toStrArray name
Expand All @@ -287,7 +300,7 @@ module RuntimeHelpers =
| :? Array as xs when
xs.GetType().GetElementType()
|> Option.ofObj
|> Option.exists(fun t -> isDateOnlyLikeType t || isTimeOnlyLikeType t || t.IsEnum)
|> Option.exists(fun t -> isDateOnlyLikeType t || isTimeOnlyLikeType t)
->
xs
|> Seq.cast<obj>
Expand Down Expand Up @@ -608,14 +621,9 @@ module RuntimeHelpers =
new HttpRequestMessage(method, Uri(requestUrl, UriKind.Relative))

let createHttpRequestFromQueryLists (httpMethod: string) (address: string) (queryParamLists: seq<#seq<string * string>>) =
let queryParams = ResizeArray<string * string>()

for queryParamList in queryParamLists do
for name, value in queryParamList do
if not(isNull value) then
queryParams.Add(name, value)

createHttpRequest httpMethod address queryParams
// Seq.concat lazily flattens the nested sequences without allocating an intermediate ResizeArray.
// createHttpRequest already filters out null values in its own loop.
createHttpRequest httpMethod address (Seq.concat queryParamLists)

let fillHeaders (msg: HttpRequestMessage) (headers: (string * string) seq) =
headers
Expand Down
47 changes: 47 additions & 0 deletions tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,53 @@ module ToQueryParamsTests =
let result = toQueryParams "times" (box values) stubClient
result |> shouldEqual [ ("times", "08:00:00") ]

[<Fact>]
let ``toQueryParams handles single integer enum value``() =
// Single CLI enum values fall through to toParam, which uses buildEnumSerializer
// (cached) to produce the underlying integer as a string.
let result =
toQueryParams "status" (box EnumToParamTests.IntStatus.Running) stubClient

result |> shouldEqual [ ("status", "2") ]

[<Fact>]
let ``toQueryParams handles single string enum value``() =
// String enums (annotated with JsonStringEnumConverter) are serialised to their
// OpenAPI wire value via the cached buildEnumSerializer.
let result =
toQueryParams "status" (box EnumToParamTests.StringStatus.Active) stubClient

result |> shouldEqual [ ("status", "active") ]

[<Fact>]
let ``toQueryParams handles string enum value with special-character wire name``() =
let result =
toQueryParams "status" (box EnumToParamTests.StringStatus.InProgress) stubClient

result |> shouldEqual [ ("status", "in-progress") ]

[<Fact>]
let ``toQueryParams handles integer enum array``() =
// Arrays of CLI enum types are handled by the dedicated enum-array branch in toQueryParams,
// which serialises each element via the cached buildEnumSerializer.
let values: EnumToParamTests.IntStatus[] =
[| EnumToParamTests.IntStatus.Pending; EnumToParamTests.IntStatus.Done |]

let result = toQueryParams "status" (box values) stubClient
result |> shouldEqual [ ("status", "1"); ("status", "3") ]

[<Fact>]
let ``toQueryParams handles string enum array``() =
// Each element is serialised to its OpenAPI wire value.
let values: EnumToParamTests.StringStatus[] =
[| EnumToParamTests.StringStatus.Active
EnumToParamTests.StringStatus.InProgress |]

let result = toQueryParams "status" (box values) stubClient

result
|> shouldEqual [ ("status", "active"); ("status", "in-progress") ]


module CombineUrlTests =

Expand Down
Loading