diff --git a/ConsumePlugin/GeneratedRestClient.fs b/ConsumePlugin/GeneratedRestClient.fs index 994578c..af40ccc 100644 --- a/ConsumePlugin/GeneratedRestClient.fs +++ b/ConsumePlugin/GeneratedRestClient.fs @@ -211,6 +211,72 @@ module PureGymApi = } |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + member _.PostStringToString (foo : Map option, ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let uri = + System.Uri ( + (match client.BaseAddress with + | null -> System.Uri "https://whatnot.com" + | v -> v), + System.Uri ("some/url", System.UriKind.Relative) + ) + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Post, + RequestUri = uri + ) + + let queryParams = + new System.Net.Http.StringContent ( + foo + |> (fun field -> + match field with + | None -> null :> System.Text.Json.Nodes.JsonNode + | Some field -> + ((fun field -> + let ret = System.Text.Json.Nodes.JsonObject () + + for (KeyValue (key, value)) in field do + ret.Add ( + key.ToString (), + System.Text.Json.Nodes.JsonValue.Create value + ) + + ret + ) + field) + :> System.Text.Json.Nodes.JsonNode + ) + |> (fun node -> if isNull node then "null" else node.ToJsonString ()) + ) + + do httpMessage.Content <- queryParams + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! jsonNode = + System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct) + |> Async.AwaitTask + + return + match jsonNode with + | null -> None + | v -> + v.AsObject () + |> Seq.map (fun kvp -> + let key = (kvp.Key) + let value = (kvp.Value).AsValue().GetValue () + key, value + ) + |> Map.ofSeq + |> Some + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + member _.GetSessions (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) = async { let! ct = Async.CancellationToken @@ -403,7 +469,9 @@ module PureGymApi = let queryParams = new System.Net.Http.StringContent ( - user |> PureGym.Member.toJsonNode |> (fun node -> node.ToJsonString ()) + user + |> PureGym.Member.toJsonNode + |> (fun node -> if isNull node then "null" else node.ToJsonString ()) ) do httpMessage.Content <- queryParams @@ -436,7 +504,7 @@ module PureGymApi = new System.Net.Http.StringContent ( user |> System.Text.Json.Nodes.JsonValue.Create - |> (fun node -> node.ToJsonString ()) + |> (fun node -> if isNull node then "null" else node.ToJsonString ()) ) do httpMessage.Content <- queryParams @@ -469,7 +537,7 @@ module PureGymApi = new System.Net.Http.StringContent ( user |> System.Text.Json.Nodes.JsonValue.Create - |> (fun node -> node.ToJsonString ()) + |> (fun node -> if isNull node then "null" else node.ToJsonString ()) ) do httpMessage.Content <- queryParams diff --git a/ConsumePlugin/RestApiExample.fs b/ConsumePlugin/RestApiExample.fs index c9c6582..ec741cd 100644 --- a/ConsumePlugin/RestApiExample.fs +++ b/ConsumePlugin/RestApiExample.fs @@ -29,6 +29,10 @@ type IPureGymApi = [] abstract GetUrl : ?ct : CancellationToken -> Task + [] + abstract PostStringToString : + [] foo : Map option * ?ct : CancellationToken -> Task option> + // We'll use this one to check handling of absolute URIs too [] abstract GetSessions : diff --git a/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs b/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs index 48ef431..2ac710a 100644 --- a/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs +++ b/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs @@ -257,3 +257,37 @@ module TestPureGymRestApi = uri.ToString () |> shouldEqual "https://patrick@en.wikipedia.org/wiki/foo" uri.UserInfo |> shouldEqual "patrick" uri.Host |> shouldEqual "en.wikipedia.org" + + [] + [] + let ``Map option example`` (isSome : bool) = + let proc (message : HttpRequestMessage) : HttpResponseMessage Async = + async { + message.Method |> shouldEqual HttpMethod.Post + + message.RequestUri.ToString () |> shouldEqual "https://whatnot.com/some/url" + let! content = message.Content.ReadAsStringAsync () |> Async.AwaitTask + + if isSome then + content |> shouldEqual """{"hi":"bye"}""" + else + content |> shouldEqual "null" + + let content = new StringContent (content) + + let resp = new HttpResponseMessage (HttpStatusCode.OK) + resp.Content <- content + return resp + } + + use client = HttpClientMock.makeNoUri proc + let api = PureGymApi.make client + + let expected = + if isSome then + [ "hi", "bye" ] |> Map.ofList |> Some + else + None + + let actual = api.PostStringToString(expected).Result + actual |> shouldEqual expected diff --git a/WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs b/WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs index da7a780..e56e97c 100644 --- a/WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs +++ b/WoofWare.Myriad.Plugins.Test/TestJsonSerialize/TestJsonSerde.fs @@ -2,6 +2,9 @@ namespace WoofWare.Myriad.Plugins.Test open System open System.Collections.Generic +open System.IO +open System.Text +open System.Text.Json open System.Text.Json.Nodes open NUnit.Framework open FsCheck diff --git a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs index a2f3a69..510e118 100644 --- a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs +++ b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs @@ -516,12 +516,18 @@ module internal HttpClientGenerator = |> SynExpr.pipeThroughFunction ( SynExpr.createLambda "node" - (SynExpr.CreateApp ( - SynExpr.CreateLongIdent ( - SynLongIdent.Create [ "node" ; "ToJsonString" ] - ), - SynExpr.CreateConst SynConst.Unit - )) + (SynExpr.ifThenElse + (SynExpr.CreateApp ( + SynExpr.CreateIdentString "isNull", + SynExpr.CreateIdentString "node" + )) + (SynExpr.CreateApp ( + SynExpr.CreateLongIdent ( + SynLongIdent.Create [ "node" ; "ToJsonString" ] + ), + SynExpr.CreateConst SynConst.Unit + )) + (SynExpr.CreateConst (SynConst.CreateString "null"))) ) ), range0 diff --git a/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs b/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs index eeb0357..75320b3 100644 --- a/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs +++ b/WoofWare.Myriad.Plugins/JsonSerializeGenerator.fs @@ -65,11 +65,14 @@ module internal JsonSerializeGenerator = SynMatchClause.Create ( SynPat.CreateLongIdent (SynLongIdent.CreateString "None", []), None, - SynExpr.CreateApp ( - SynExpr.CreateLongIdent ( - SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonValue" ; "Create" ] - ), - SynExpr.CreateNull + // The absolutely galaxy-brained implementation of JsonValue has `JsonValue.Parse "null"` + // identically equal to null. We have to work around this later, but we might as well just + // be efficient here and whip up the null directly. + SynExpr.CreateNull + |> SynExpr.upcast' ( + SynType.CreateLongIdent ( + SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ] + ) ) ) @@ -80,6 +83,12 @@ module internal JsonSerializeGenerator = ), None, SynExpr.CreateApp (serializeNode ty, SynExpr.CreateIdentString "field") + |> SynExpr.CreateParen + |> SynExpr.upcast' ( + SynType.CreateLongIdent ( + SynLongIdent.Create [ "System" ; "Text" ; "Json" ; "Nodes" ; "JsonNode" ] + ) + ) ) ] ) diff --git a/WoofWare.Myriad.Plugins/SynExpr.fs b/WoofWare.Myriad.Plugins/SynExpr.fs index cde2731..d93a545 100644 --- a/WoofWare.Myriad.Plugins/SynExpr.fs +++ b/WoofWare.Myriad.Plugins/SynExpr.fs @@ -263,6 +263,8 @@ module internal SynExpr = |> callMethodArg "ToString" (SynExpr.CreateConstString "yyyy-MM-ddTHH:mm:ss") | _ -> callMethod "ToString" ident + let upcast' (ty : SynType) (e : SynExpr) = SynExpr.Upcast (e, ty, range0) + let synBindingTriviaZero (isMember : bool) = { SynBindingTrivia.EqualsRange = Some range0