diff --git a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs index 9f2390c..340ae49 100644 --- a/src/SwaggerProvider.Runtime/RuntimeHelpers.fs +++ b/src/SwaggerProvider.Runtime/RuntimeHelpers.fs @@ -312,8 +312,10 @@ module RuntimeHelpers = | :? IO.Stream as stream -> addFileStream name stream | :? (IO.Stream[]) as streams -> streams |> Seq.iter(addFileStream name) | x -> - let strValue = x.ToString() // TODO: serialize? does not work with arrays probably - cnt.Add(toStringContent strValue, name) + let strValue = toParam x + + if not(isNull strValue) then + cnt.Add(toStringContent strValue, name) cnt @@ -321,7 +323,13 @@ module RuntimeHelpers = let keyValues = keyValues |> Seq.filter(snd >> isNull >> not) - |> Seq.map(fun (k, v) -> Collections.Generic.KeyValuePair(k, v.ToString())) + |> Seq.choose(fun (k, v) -> + let param = toParam v + + if isNull param then + None + else + Some(Collections.Generic.KeyValuePair(k, param))) new FormUrlEncodedContent(keyValues) diff --git a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs index e2d0b10..7adb271 100644 --- a/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs +++ b/tests/SwaggerProvider.Tests/RuntimeHelpersTests.fs @@ -738,6 +738,65 @@ module ToFormUrlEncodedContentTests = body |> shouldEqual "" } + [] + let ``toFormUrlEncodedContent formats DateTime as ISO 8601``() = + task { + let dt = DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc) + + use content = toFormUrlEncodedContent(seq { ("ts", box dt) }) + + let! body = content.ReadAsStringAsync() + let encodedValue = body.Substring("ts=".Length) + let decodedValue = WebUtility.UrlDecode(encodedValue) + + decodedValue |> shouldEqual(dt.ToString("O")) + } + + [] + let ``toFormUrlEncodedContent formats DateTimeOffset as ISO 8601``() = + task { + let dto = DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.Zero) + + use content = toFormUrlEncodedContent(seq { ("ts", box dto) }) + + let! body = content.ReadAsStringAsync() + let encodedValue = body.Substring("ts=".Length) + let decodedValue = WebUtility.UrlDecode(encodedValue) + + decodedValue |> shouldEqual(dto.ToString("O")) + } + + [] + let ``toFormUrlEncodedContent formats DateOnly as ISO 8601``() = + task { + let d = DateOnly(2025, 7, 4) + use content = toFormUrlEncodedContent(seq { ("date", box d) }) + + let! body = content.ReadAsStringAsync() + let encodedValue = body.Substring("date=".Length) + let decodedValue = WebUtility.UrlDecode(encodedValue) + + decodedValue |> shouldEqual "2025-07-04" + } + + [] + let ``toFormUrlEncodedContent skips values when toParam returns null``() = + task { + let nestedNone = box(Some(None: string option)) + + use content = + toFormUrlEncodedContent( + seq { + ("present", box "yes") + ("nestedNone", nestedNone) + } + ) + + let! body = content.ReadAsStringAsync() + body |> shouldContainText "present=yes" + body |> shouldNotContainText "nestedNone" + } + module ToMultipartFormDataContentTests = @@ -778,6 +837,59 @@ module ToMultipartFormDataContentTests = hasFileName |> shouldEqual true + [] + let ``toMultipartFormDataContent formats DateTime as ISO 8601``() = + task { + let dt = DateTime(2024, 6, 15, 10, 30, 0, DateTimeKind.Utc) + use content = toMultipartFormDataContent(seq { ("ts", box dt) }) + let part = content |> Seq.exactlyOne + let! body = part.ReadAsStringAsync() + body |> shouldEqual(dt.ToString("O")) + } + + [] + let ``toMultipartFormDataContent formats DateTimeOffset as ISO 8601``() = + task { + let dto = DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.Zero) + use content = toMultipartFormDataContent(seq { ("ts", box dto) }) + let part = content |> Seq.exactlyOne + let! body = part.ReadAsStringAsync() + body |> shouldEqual(dto.ToString("O")) + } + + [] + let ``toMultipartFormDataContent formats DateOnly as ISO 8601``() = + task { + let d = DateOnly(2025, 7, 4) + use content = toMultipartFormDataContent(seq { ("date", box d) }) + let part = content |> Seq.exactlyOne + let! body = part.ReadAsStringAsync() + body |> shouldEqual "2025-07-04" + } + + [] + let ``toMultipartFormDataContent skips values when toParam returns null``() = + task { + let nestedNone = box(Some(None: string option)) + + use content = + toMultipartFormDataContent( + seq { + ("present", box "yes") + ("nestedNone", nestedNone) + } + ) + + content |> Seq.length |> shouldEqual 1 + let part = content |> Seq.exactlyOne + let! body = part.ReadAsStringAsync() + + part.Headers.ContentDisposition.Name.Trim('"') + |> shouldEqual "present" + + body |> shouldEqual "yes" + } + /// Test types for getPropertyValues tests. type PropValWithAttr(value: string) =