From e659d581b6763752aaa2520e77a97d93d6f36d7b Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Sat, 1 Dec 2018 18:25:46 -0500 Subject: [PATCH 1/2] Intial sketch of a port to http 2.0.0 --- elm.json | 4 +- src/GraphQL/Client/Http.elm | 177 ++++--------------------------- src/GraphQL/Client/Http/Util.elm | 16 +-- 3 files changed, 26 insertions(+), 171 deletions(-) diff --git a/elm.json b/elm.json index adc5c75..9c913fc 100644 --- a/elm.json +++ b/elm.json @@ -13,11 +13,11 @@ "elm-version": "0.19.0 <= v < 0.20.0", "dependencies": { "elm/core": "1.0.0 <= v < 2.0.0", - "elm/http": "1.0.0 <= v < 2.0.0", + "elm/http": "2.0.0 <= v < 3.0.0", "elm/json": "1.0.0 <= v < 2.0.0", "elm/url": "1.0.0 <= v < 2.0.0" }, "test-dependencies": { "elm-explorations/test": "1.0.0 <= v < 2.0.0" } -} \ No newline at end of file +} diff --git a/src/GraphQL/Client/Http.elm b/src/GraphQL/Client/Http.elm index d55f774..3691831 100644 --- a/src/GraphQL/Client/Http.elm +++ b/src/GraphQL/Client/Http.elm @@ -1,20 +1,9 @@ -module GraphQL.Client.Http - exposing - ( RequestError - , DocumentLocation - , Error(..) - , RequestOptions - , sendQuery - , sendMutation - , customSendQuery - , customSendQueryRaw - , customSendMutation - , customSendMutationRaw - ) +module GraphQL.Client.Http exposing (Error(..), RequestError, DocumentLocation, sendQuery, sendMutation, RequestOptions) {-| The functions in this module let you perform HTTP requests to conventional GraphQL server endpoints. -@docs Error, RequestError, DocumentLocation, sendQuery, sendMutation, RequestOptions, customSendQuery, customSendMutation, customSendQueryRaw, customSendMutationRaw +@docs Error, RequestError, DocumentLocation, sendQuery, sendMutation, RequestOptions, customSendQuery, customSendMutation, customSendQueryRaw, customSendMutationRaw + -} import GraphQL.Client.Http.Util as Util @@ -51,41 +40,23 @@ type Error sendQuery : String -> Builder.Request Builder.Query result - -> Task Error result + -> (Result Error result -> msg) + -> Cmd msg sendQuery = Util.defaultRequestOptions >> send -{-| Takes a URL and a `Query` `Request` and returns a `Task` that you can perform with `Task.attempt` which will send a `POST` request to a GraphQL server at the given endpoint and return raw `Http.Response` in Task. --} -sendQueryRaw : - String - -> Builder.Request Builder.Query result - -> Task Error (Http.Response String) -sendQueryRaw = - Util.defaultRequestOptions >> sendExpecting rawExpect - - {-| Takes a URL and a `Mutation` `Request` and returns a `Task` that you can perform with `Task.attempt` which will send a `POST` request to a GraphQL server at the given endpoint. -} sendMutation : String -> Builder.Request Builder.Mutation result - -> Task Error result + -> (Result Error result -> msg) + -> Cmd msg sendMutation = Util.defaultRequestOptions >> send -{-| Takes a URL and a `Mutation` `Request` and returns a `Task` that you can perform with `Task.attempt` which will send a `POST` request to a GraphQL server at the given endpoint and return raw `Http.Response` in Task. --} -sendMutationRaw : - String - -> Builder.Request Builder.Mutation result - -> Task Error (Http.Response String) -sendMutationRaw = - Util.defaultRequestOptions >> sendExpecting rawExpect - - {-| Options available for customizing GraphQL HTTP requests. `method` should be either `"GET"` or `"POST"`. For `GET` requests, the `url` is modified to include extra parameters in the query string for the GraphQL document and variables. Otherwise, the document and variables are included in the HTTP request body. -} type alias RequestOptions = @@ -93,140 +64,30 @@ type alias RequestOptions = , headers : List Http.Header , url : String , timeout : Maybe Float - , withCredentials : Bool } -{-| Like `sendQuery`, but takes an `RequestOptions` value instead of a URL to let you further customize the HTTP request. --} -customSendQuery : - RequestOptions - -> Builder.Request Builder.Query result - -> Task Error result -customSendQuery = - send - - -{-| Like `sendQuery`, but takes an `RequestOptions` value instead of a URL to let you further customize the HTTP request. You will get a plain `Http.Response` as Task result. - -Useful for things like caching, custom errors decoding, etc. - -Example of response decoding: - - let - decoder = - GraphQL.Request.Builder.responseDataDecoder request - |> Json.Decode.field "data" - - options = - { method = "GET" - , headers = [] - , url = "/graphql" - , timeout = Nothing - , withCredentials = False - } - in - request - |> GraphQL.Client.Http.customSendQueryRaw options - |> Task.andThen - (\response -> - case Json.Decode.decodeString decoder response.body of - Err err -> - Task.fail <| GraphQL.Client.Http.HttpError <| Http.BadPayload err response - - Ok decodedValue -> - Task.succeed decodedValue - ) --} -customSendQueryRaw : - RequestOptions - -> Builder.Request Builder.Query result - -> Task Error (Http.Response String) -customSendQueryRaw = - sendExpecting rawExpect - - -{-| Like `sendMutation`, but takes an `RequestOptions` value instead of a URL to let you further customize the HTTP request. --} -customSendMutation : - RequestOptions - -> Builder.Request Builder.Mutation result - -> Task Error result -customSendMutation = - send - - -{-| Like `sendMutation`, but takes an `RequestOptions` value instead of a URL to let you further customize the HTTP request. You will get a plain `Http.Response` as Task result. - -Useful for things like custom errors decoding, etc. - -Example of response decoding: - - let - decoder = - GraphQL.Request.Builder.responseDataDecoder mutationRequest - |> Json.Decode.field "data" - - options = - { method = "GET" - , headers = [] - , url = "/graphql" - , timeout = Nothing - , withCredentials = False - } - in - mutationRequest - |> GraphQL.Client.Http.customSendMutationRaw options - |> Task.andThen - (\response -> - case Json.Decode.decodeString decoder response.body of - Err err -> - Task.fail <| GraphQL.Client.Http.HttpError <| Http.BadPayload err response - - Ok decodedValue -> - Task.succeed decodedValue - ) - --} -customSendMutationRaw : - RequestOptions - -> Builder.Request Builder.Mutation result - -> Task Error (Http.Response String) -customSendMutationRaw = - sendExpecting rawExpect - - -rawExpect : Http.Expect (Http.Response String) -rawExpect = - Http.expectStringResponse Ok - - send : RequestOptions -> Builder.Request operationType result - -> Task Error result -send options request = + -> (Result Error result -> msg) + -> Cmd msg +send options request toMsg = let expect = - Util.defaultExpect (Builder.responseDataDecoder request) - in - sendExpecting expect options request - + Http.expectJson + (Result.mapError (Util.convertHttpError HttpError GraphQLError) >> toMsg) + (Util.dataDecoder (Builder.responseDataDecoder request)) -sendExpecting : - Http.Expect result - -> RequestOptions - -> Builder.Request operationType result2 - -> Task Error result -sendExpecting expect requestOptions request = - let documentString = Builder.requestBody request variableValues = Builder.jsonVariableValues request in - Util.requestConfig requestOptions documentString expect variableValues - |> Http.request - |> Http.toTask - |> Task.mapError (Util.convertHttpError HttpError GraphQLError) + Util.requestConfig options documentString expect variableValues + |> Http.request + + + +-- |> Task.mapError ( diff --git a/src/GraphQL/Client/Http/Util.elm b/src/GraphQL/Client/Http/Util.elm index 56c6661..1793773 100644 --- a/src/GraphQL/Client/Http/Util.elm +++ b/src/GraphQL/Client/Http/Util.elm @@ -54,7 +54,6 @@ type alias RequestOptions = , headers : List Http.Header , url : String , timeout : Maybe Float - , withCredentials : Bool } @@ -82,7 +81,7 @@ type alias RequestConfig a = , body : Http.Body , expect : Http.Expect a , timeout : Maybe Float - , withCredentials : Bool + , tracker: Maybe String } @@ -92,7 +91,6 @@ defaultRequestOptions url = , headers = [] , url = url , timeout = Nothing - , withCredentials = False } @@ -116,13 +114,12 @@ requestConfig requestOptions documentString expect variableValues = , body = body , expect = expect , timeout = requestOptions.timeout - , withCredentials = requestOptions.withCredentials + , tracker = Nothing } -defaultExpect : Json.Decode.Decoder result -> Http.Expect result -defaultExpect = - Http.expectJson << Json.Decode.field "data" +dataDecoder = + Json.Decode.field "data" errorsResponseDecoder : Json.Decode.Decoder (List RequestError) @@ -140,10 +137,7 @@ convertHttpError wrapHttpError wrapGraphQLError httpError = |> Result.withDefault (wrapHttpError httpError) in case httpError of - Http.BadStatus { body } -> - handleErrorWithResponseBody body - - Http.BadPayload _ { body } -> + Http.BadBody body -> handleErrorWithResponseBody body _ -> From 79cb5c8c8b1c5aab3214ac0b548cece5164c7a0b Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Sat, 1 Dec 2018 19:02:19 -0500 Subject: [PATCH 2/2] Experiment with an expectGraphQL --- src/GraphQL/Client/Http.elm | 42 +++++++++++++++++++++++--- src/GraphQL/Client/Http/Util.elm | 52 +++++++++----------------------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/GraphQL/Client/Http.elm b/src/GraphQL/Client/Http.elm index 3691831..d5732a8 100644 --- a/src/GraphQL/Client/Http.elm +++ b/src/GraphQL/Client/Http.elm @@ -8,7 +8,9 @@ module GraphQL.Client.Http exposing (Error(..), RequestError, DocumentLocation, import GraphQL.Client.Http.Util as Util import GraphQL.Request.Builder as Builder +import GraphQL.Response as Response import Http +import Json.Decode import Task exposing (Task) @@ -75,9 +77,7 @@ send : send options request toMsg = let expect = - Http.expectJson - (Result.mapError (Util.convertHttpError HttpError GraphQLError) >> toMsg) - (Util.dataDecoder (Builder.responseDataDecoder request)) + expectGraphQL toMsg request documentString = Builder.requestBody request @@ -89,5 +89,39 @@ send options request toMsg = |> Http.request +expectGraphQL : + (Result Error result -> msg) + -> Builder.Request operationType result + -> Http.Expect msg +expectGraphQL toMsg request = + let + decoder = + Json.Decode.map2 (\errors data -> ( errors, data )) + (Json.Decode.maybe (Json.Decode.field "errors" Response.errorsDecoder)) + (Json.Decode.field "data" (Builder.responseDataDecoder request)) + in + Http.expectStringResponse toMsg <| + \response -> + case response of + Http.BadUrl_ url -> + Err (HttpError (Http.BadUrl url)) + + Http.Timeout_ -> + Err (HttpError Http.Timeout) + + Http.NetworkError_ -> + Err (HttpError Http.NetworkError) + + Http.BadStatus_ metadata body -> + Err (HttpError (Http.BadStatus metadata.statusCode)) + + Http.GoodStatus_ metadata body -> + case Json.Decode.decodeString decoder body of + Ok ( Just errors, _ ) -> + Err (GraphQLError errors) + + Ok ( Nothing, data ) -> + Ok data --- |> Task.mapError ( + Err err -> + Err (HttpError (Http.BadBody (Json.Decode.errorToString err))) diff --git a/src/GraphQL/Client/Http/Util.elm b/src/GraphQL/Client/Http/Util.elm index 1793773..cab0770 100644 --- a/src/GraphQL/Client/Http/Util.elm +++ b/src/GraphQL/Client/Http/Util.elm @@ -1,4 +1,4 @@ -module GraphQL.Client.Http.Util exposing (..) +module GraphQL.Client.Http.Util exposing (DocumentLocation, Error(..), RequestConfig, RequestError, RequestOptions, defaultRequestOptions, parameterizedUrl, postBody, postBodyJson, requestConfig) import GraphQL.Response as Response import Http @@ -18,7 +18,7 @@ postBodyJson documentString variableValues = |> Maybe.map (\obj -> [ ( "variables", obj ) ]) |> Maybe.withDefault [] in - Json.Encode.object ([ ( "query", documentValue ) ] ++ extraParams) + Json.Encode.object ([ ( "query", documentValue ) ] ++ extraParams) postBody : String -> Maybe Json.Encode.Value -> Http.Body @@ -32,6 +32,7 @@ parameterizedUrl url documentString variableValues = firstParamPrefix = if String.contains "?" url then "&" + else "?" @@ -46,7 +47,7 @@ parameterizedUrl url documentString variableValues = ) |> Maybe.withDefault "" in - url ++ queryParam ++ variablesParam + url ++ queryParam ++ variablesParam type alias RequestOptions = @@ -81,7 +82,7 @@ type alias RequestConfig a = , body : Http.Body , expect : Http.Expect a , timeout : Maybe Float - , tracker: Maybe String + , tracker : Maybe String } @@ -105,40 +106,15 @@ requestConfig requestOptions documentString expect variableValues = ( url, body ) = if requestOptions.method == "GET" then ( parameterizedUrl requestOptions.url documentString variableValues, Http.emptyBody ) + else ( requestOptions.url, postBody documentString variableValues ) in - { method = requestOptions.method - , headers = requestOptions.headers - , url = url - , body = body - , expect = expect - , timeout = requestOptions.timeout - , tracker = Nothing - } - - -dataDecoder = - Json.Decode.field "data" - - -errorsResponseDecoder : Json.Decode.Decoder (List RequestError) -errorsResponseDecoder = - Json.Decode.field "errors" Response.errorsDecoder - - -convertHttpError : (Http.Error -> err) -> (List RequestError -> err) -> Http.Error -> err -convertHttpError wrapHttpError wrapGraphQLError httpError = - let - handleErrorWithResponseBody responseBody = - responseBody - |> Json.Decode.decodeString errorsResponseDecoder - |> Result.map wrapGraphQLError - |> Result.withDefault (wrapHttpError httpError) - in - case httpError of - Http.BadBody body -> - handleErrorWithResponseBody body - - _ -> - wrapHttpError httpError + { method = requestOptions.method + , headers = requestOptions.headers + , url = url + , body = body + , expect = expect + , timeout = requestOptions.timeout + , tracker = Nothing + }