From 91136a25abd2d204c79aa4e668326bb898cd9988 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Tue, 30 Apr 2024 20:03:20 +0100 Subject: [PATCH] Enable query params in Get request endpoint (#131) --- ConsumePlugin/GeneratedRestClient.fs | 47 ++++++++++++++++++- ConsumePlugin/RestApiExample.fs | 4 ++ .../TestHttpClient/TestPureGymRestApi.fs | 27 +++++++++++ .../HttpClientGenerator.fs | 23 ++++++++- WoofWare.Myriad.Plugins/SynExpr.fs | 16 +++++++ 5 files changed, 115 insertions(+), 2 deletions(-) diff --git a/ConsumePlugin/GeneratedRestClient.fs b/ConsumePlugin/GeneratedRestClient.fs index c1a99b5..49c3f12 100644 --- a/ConsumePlugin/GeneratedRestClient.fs +++ b/ConsumePlugin/GeneratedRestClient.fs @@ -288,7 +288,52 @@ module PureGymApi = | v -> v), System.Uri ( ("/v2/gymSessions/member" - + "?fromDate=" + + (if "/v2/gymSessions/member".IndexOf (char 63) > 0 then + "&" + else + "?") + + "fromDate=" + + ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode) + + "&toDate=" + + ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)), + System.UriKind.Relative + ) + ) + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = uri + ) + + 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 Sessions.jsonParse jsonNode + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetSessionsWithQuery (fromDate : DateOnly, toDate : DateOnly, 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 ( + ("/v2/gymSessions/member?foo=1" + + (if "/v2/gymSessions/member?foo=1".IndexOf (char 63) > 0 then + "&" + else + "?") + + "fromDate=" + ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode) + "&toDate=" + ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)), diff --git a/ConsumePlugin/RestApiExample.fs b/ConsumePlugin/RestApiExample.fs index c3cc673..a0cd719 100644 --- a/ConsumePlugin/RestApiExample.fs +++ b/ConsumePlugin/RestApiExample.fs @@ -38,6 +38,10 @@ type IPureGymApi = abstract GetSessions : [] fromDate : DateOnly * [] toDate : DateOnly * ?ct : CancellationToken -> Task + [] + abstract GetSessionsWithQuery : + [] fromDate : DateOnly * [] toDate : DateOnly * ?ct : CancellationToken -> Task + // An example from RestEase's own docs [] abstract CreateUserString : [] user : string * ?ct : CancellationToken -> Task diff --git a/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs b/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs index 2ac710a..2b10930 100644 --- a/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs +++ b/WoofWare.Myriad.Plugins.Test/TestHttpClient/TestPureGymRestApi.fs @@ -234,6 +234,33 @@ module TestPureGymRestApi = api.GetSessions(startDate, endDate).Result |> shouldEqual expected + [] + let ``Test GetSessionsWithQuery`` + (baseUri : Uri, (startDate : DateOnly, (endDate : DateOnly, (json : string, expected : Sessions)))) + = + let proc (message : HttpRequestMessage) : HttpResponseMessage Async = + async { + message.Method |> shouldEqual HttpMethod.Get + + // This one is specified as being absolute, in its attribute on the IPureGymApi type + let expectedUri = + let fromDate = dateOnlyToString startDate + let toDate = dateOnlyToString endDate + $"https://example.com/v2/gymSessions/member?foo=1&fromDate=%s{fromDate}&toDate=%s{toDate}" + + message.RequestUri.ToString () |> shouldEqual expectedUri + + let content = new StringContent (json) + let resp = new HttpResponseMessage (HttpStatusCode.OK) + resp.Content <- content + return resp + } + + use client = HttpClientMock.make baseUri proc + let api = PureGymApi.make client + + api.GetSessionsWithQuery(startDate, endDate).Result |> shouldEqual expected + [] let ``URI example`` () = let proc (message : HttpRequestMessage) : HttpResponseMessage Async = diff --git a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs index e688e6c..c36824c 100644 --- a/WoofWare.Myriad.Plugins/HttpClientGenerator.fs +++ b/WoofWare.Myriad.Plugins/HttpClientGenerator.fs @@ -308,6 +308,27 @@ module internal HttpClientGenerator = | None -> failwith "Unable to get parameter variable name from anonymous parameter" | Some id -> id + let urlSeparator = + // apparent Myriad bug: `IndexOf '?'` gets formatted as `IndexOf ?` which is clearly wrong + let questionMark = + SynExpr.CreateParen ( + SynExpr.CreateApp ( + SynExpr.CreateIdentString "char", + SynExpr.CreateConst (SynConst.Int32 63) + ) + ) + + let containsQuestion = + info.UrlTemplate + |> SynExpr.callMethodArg "IndexOf" questionMark + |> SynExpr.greaterThanOrEqual (SynExpr.CreateConst (SynConst.Int32 0)) + + SynExpr.ifThenElse + containsQuestion + (SynExpr.CreateConst (SynConst.CreateString "?")) + (SynExpr.CreateConst (SynConst.CreateString "&")) + |> SynExpr.CreateParen + let prefix = SynExpr.CreateIdent firstValueId |> SynExpr.toString firstValue.Type @@ -316,7 +337,7 @@ module internal HttpClientGenerator = SynExpr.CreateLongIdent (SynLongIdent.Create [ "System" ; "Web" ; "HttpUtility" ; "UrlEncode" ]) ) |> SynExpr.CreateParen - |> SynExpr.plus (SynExpr.CreateConstString ("?" + firstKey + "=")) + |> SynExpr.plus (SynExpr.plus urlSeparator (SynExpr.CreateConstString (firstKey + "="))) (prefix, queryParams) ||> List.fold (fun uri (paramKey, paramValue) -> diff --git a/WoofWare.Myriad.Plugins/SynExpr.fs b/WoofWare.Myriad.Plugins/SynExpr.fs index e5c9d46..04858c1 100644 --- a/WoofWare.Myriad.Plugins/SynExpr.fs +++ b/WoofWare.Myriad.Plugins/SynExpr.fs @@ -311,3 +311,19 @@ module internal SynExpr = ), x ) + + /// {y} >= {x} + let greaterThanOrEqual (x : SynExpr) (y : SynExpr) : SynExpr = + SynExpr.CreateApp ( + SynExpr.CreateAppInfix ( + SynExpr.CreateLongIdent ( + SynLongIdent.SynLongIdent ( + [ Ident.Create "op_GreaterThanOrEqual" ], + [], + [ Some (IdentTrivia.OriginalNotation ">=") ] + ) + ), + y + ), + x + )