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..d5732a8 100644 --- a/src/GraphQL/Client/Http.elm +++ b/src/GraphQL/Client/Http.elm @@ -1,25 +1,16 @@ -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 import GraphQL.Request.Builder as Builder +import GraphQL.Response as Response import Http +import Json.Decode import Task exposing (Task) @@ -51,41 +42,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 +66,62 @@ 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 : +send : 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: - + -> Builder.Request operationType result + -> (Result Error result -> msg) + -> Cmd msg +send options request toMsg = 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 - + expect = + expectGraphQL toMsg request -{-| 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. + documentString = + Builder.requestBody request -Useful for things like custom errors decoding, etc. + variableValues = + Builder.jsonVariableValues request + in + Util.requestConfig options documentString expect variableValues + |> Http.request -Example of response decoding: +expectGraphQL : + (Result Error result -> msg) + -> Builder.Request operationType result + -> Http.Expect msg +expectGraphQL toMsg request = let decoder = - GraphQL.Request.Builder.responseDataDecoder mutationRequest - |> Json.Decode.field "data" - - options = - { method = "GET" - , headers = [] - , url = "/graphql" - , timeout = Nothing - , withCredentials = False - } + Json.Decode.map2 (\errors data -> ( errors, data )) + (Json.Decode.maybe (Json.Decode.field "errors" Response.errorsDecoder)) + (Json.Decode.field "data" (Builder.responseDataDecoder request)) 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 + Http.expectStringResponse toMsg <| + \response -> + case response of + Http.BadUrl_ url -> + Err (HttpError (Http.BadUrl url)) + Http.Timeout_ -> + Err (HttpError Http.Timeout) -rawExpect : Http.Expect (Http.Response String) -rawExpect = - Http.expectStringResponse Ok + Http.NetworkError_ -> + Err (HttpError Http.NetworkError) + Http.BadStatus_ metadata body -> + Err (HttpError (Http.BadStatus metadata.statusCode)) -send : - RequestOptions - -> Builder.Request operationType result - -> Task Error result -send options request = - let - expect = - Util.defaultExpect (Builder.responseDataDecoder request) - in - sendExpecting expect options request + Http.GoodStatus_ metadata body -> + case Json.Decode.decodeString decoder body of + Ok ( Just errors, _ ) -> + Err (GraphQLError errors) + Ok ( Nothing, data ) -> + Ok data -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) + 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 56c6661..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 = @@ -54,7 +55,6 @@ type alias RequestOptions = , headers : List Http.Header , url : String , timeout : Maybe Float - , withCredentials : Bool } @@ -82,7 +82,7 @@ type alias RequestConfig a = , body : Http.Body , expect : Http.Expect a , timeout : Maybe Float - , withCredentials : Bool + , tracker : Maybe String } @@ -92,7 +92,6 @@ defaultRequestOptions url = , headers = [] , url = url , timeout = Nothing - , withCredentials = False } @@ -107,44 +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 - , withCredentials = requestOptions.withCredentials - } - - -defaultExpect : Json.Decode.Decoder result -> Http.Expect result -defaultExpect = - Http.expectJson << 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.BadStatus { body } -> - handleErrorWithResponseBody body - - Http.BadPayload _ { body } -> - handleErrorWithResponseBody body - - _ -> - wrapHttpError httpError + { method = requestOptions.method + , headers = requestOptions.headers + , url = url + , body = body + , expect = expect + , timeout = requestOptions.timeout + , tracker = Nothing + }