diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index aaa94854..c7f07e3a 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -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", 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 ] | :? array as xs -> xs |> toStrArray name | :? array as xs -> xs |> toStrArray name | :? array as xs -> xs |> toStrArray name @@ -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 @@ -608,14 +621,9 @@ module RuntimeHelpers = new HttpRequestMessage(method, Uri(requestUrl, UriKind.Relative)) let createHttpRequestFromQueryLists (httpMethod: string) (address: string) (queryParamLists: seq<#seq>) = - let queryParams = ResizeArray() - - 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 diff --git a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs index 5e70792b..b45c9f7a 100644 --- a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs +++ b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs @@ -509,6 +509,53 @@ module ToQueryParamsTests = let result = toQueryParams "times" (box values) stubClient result |> shouldEqual [ ("times", "08:00:00") ] + [] + 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") ] + + [] + 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") ] + + [] + 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") ] + + [] + 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") ] + + [] + 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 =