This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.

Graphqelm.Http

Send requests to your GraphQL endpoint. See this live code demo or the examples/ folder for some end-to-end examples. The builder syntax is inspired by Luke Westby's elm-http-builder package.

Data Types

type Request decodesTo = Request { details : Details decodesTo , headers : List Http.Header , baseUrl : String , expect : Json.Decode.Decoder decodesTo , timeout : Maybe Time , withCredentials : Bool , queryParams : List ( String, String ) }

An internal request as it's built up. Once it's built up, send the request with Graphqelm.Http.send.

type Error parsedData = GraphqlError (GraphqlError.PossiblyParsedData parsedData) (List GraphqlError.GraphqlError) | HttpError Http.Error

Represents the two types of errors you can get, an Http error or a GraphQL error. See the Graphqelm.Http.GraphqlError module docs for more details.

Begin Request Pipeline

queryRequest : String -> SelectionSet decodesTo RootQuery -> Request decodesTo

Initialize a basic request from a Query. You can add on options with withHeader, withTimeout, withCredentials, and send it with Graphqelm.Http.send.

mutationRequest : String -> SelectionSet decodesTo RootMutation -> Request decodesTo

Initialize a basic request from a Mutation. You can add on options with withHeader, withTimeout, withCredentials, and send it with Graphqelm.Http.send.

queryRequestWithHttpGet : String -> QueryRequestMethod -> SelectionSet decodesTo RootQuery -> Request decodesTo

Exactly like queryRequest, but with an option to use the HTTP GET request method. You will probably want to use GetIfShortEnough, which uses GET if the full URL ends up being under 2000 characters, or POST otherwise, since some browsers don't support URLs over a certain length. GetIfShortEnough will typically do what you need. If you must use GET no matter what when hitting your endpoint, you can use AlwaysGet.

queryRequest always uses POST since some GraphQL API's don't support GET requests (for example, the Github API assumes that you are doing an introspection query if you make a GET request). But for semantic reasons, GET requests are sometimes useful for sending GraphQL Query requests. That is, a GraphQL Query does not perform side-effects on the server like a Mutation does, so a GET indicates this and allows some servers to cache requests. See this github thread from the Apollo project for more details.

type QueryRequestMethod = AlwaysGet | GetIfShortEnough

Union type to pass in to queryRequestWithHttpGet. Only applies to queries. Mutations don't accept this configuration option and will always use POST.

Configure Request Options

withHeader : String -> String -> Request decodesTo -> Request decodesTo

Add a header.

makeRequest : Cmd Msg
makeRequest =
    query
        |> Graphqelm.Http.queryRequest "https://api.github.com/graphql"
        |> Graphqelm.Http.withHeader "authorization" "Bearer <my token>"
        |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)
withTimeout : Time -> Request decodesTo -> Request decodesTo

Add a timeout.

withCredentials : Request decodesTo -> Request decodesTo

Set with credentials to true.

withQueryParams : List ( String, String ) -> Request decodesTo -> Request decodesTo

Add query params. The values will be Uri encoded.

makeRequest : Cmd Msg
makeRequest =
    query
        |> Graphqelm.Http.queryRequest "https://api.github.com/graphql"
        |> Graphqelm.Http.withQueryParams [ ( "version", "1.2.3" ) ]
        |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

Perform Request

send : (Result (Error decodesTo) decodesTo -> msg) -> Request decodesTo -> Cmd msg

Send the Graphqelm.Request You can use it on its own, or with a library like RemoteData.

import Graphqelm.Http
import Graphqelm.OptionalArgument exposing (OptionalArgument(Null, Present))
import RemoteData exposing (RemoteData)

type Msg
    = GotResponse RemoteData Graphqelm.Http.Error Response

makeRequest : Cmd Msg
makeRequest =
    query
        |> Graphqelm.Http.queryRequest "https://graphqelm.herokuapp.com/"
        |> Graphqelm.Http.withHeader "authorization" "Bearer abcdefgh12345678"
        -- If you're not using remote data, it's just
        -- |> Graphqelm.Http.send GotResponse
        -- With remote data, it's as below
        |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

If any errors are present, you will get a GraphqlError that includes the details. GraphQL allows for partial data to be returned in the case of errors so you can inspect the data returned in the GraphqlError if you would like to try to recover any data that made it through in the response.

toTask : Request decodesTo -> Task (Error decodesTo) decodesTo

Convert a Request to a Task. See Graphqelm.Http.send for an example of how to build up a Request.

Map Errors

mapError : (a -> b) -> Error a -> Error b

Map the error data if it is ParsedData.

ignoreParsedErrorData : Error parsedData -> Error ()

Useful when you don't want to deal with the recovered data if there is ParsedData. Just a shorthand for mapError that will turn any ParsedData into ().

module Graphqelm.Http exposing (Error(..), QueryRequestMethod(..), Request, ignoreParsedErrorData, mapError, mutationRequest, queryRequest, queryRequestWithHttpGet, send, toTask, withCredentials, withHeader, withQueryParams, withTimeout)

{-| Send requests to your GraphQL endpoint. See [this live code demo](https://rebrand.ly/graphqelm)
or the [`examples/`](https://github.com/dillonkearns/graphqelm/tree/master/examples)
folder for some end-to-end examples.
The builder syntax is inspired by Luke Westby's
[elm-http-builder package](http://package.elm-lang.org/packages/lukewestby/elm-http-builder/latest).


## Data Types

@docs Request, Error


## Begin `Request` Pipeline

@docs queryRequest, mutationRequest, queryRequestWithHttpGet
@docs QueryRequestMethod


## Configure `Request` Options

@docs withHeader, withTimeout, withCredentials, withQueryParams


## Perform `Request`

@docs send, toTask


## Map `Error`s

@docs mapError, ignoreParsedErrorData

-}

import Graphqelm.Document as Document
import Graphqelm.Http.GraphqlError as GraphqlError
import Graphqelm.Http.QueryHelper as QueryHelper
import Graphqelm.Http.QueryParams as QueryParams
import Graphqelm.Operation exposing (RootMutation, RootQuery)
import Graphqelm.SelectionSet exposing (SelectionSet)
import Http
import Json.Decode
import Json.Encode
import Task exposing (Task)
import Time exposing (Time)


{-| An internal request as it's built up. Once it's built up, send the
request with `Graphqelm.Http.send`.
-}
type Request decodesTo
    = Request
        { details : Details decodesTo
        , headers : List Http.Header
        , baseUrl : String
        , expect : Json.Decode.Decoder decodesTo
        , timeout : Maybe Time
        , withCredentials : Bool
        , queryParams : List ( String, String )
        }


{-| Union type to pass in to `queryRequestWithHttpGet`. Only applies to queries.
Mutations don't accept this configuration option and will always use POST.
-}
type QueryRequestMethod
    = AlwaysGet
    | GetIfShortEnough


type Details decodesTo
    = Query (Maybe QueryRequestMethod) (SelectionSet decodesTo RootQuery)
    | Mutation (SelectionSet decodesTo RootMutation)


{-| Initialize a basic request from a Query. You can add on options with `withHeader`,
`withTimeout`, `withCredentials`, and send it with `Graphqelm.Http.send`.
-}
queryRequest : String -> SelectionSet decodesTo RootQuery -> Request decodesTo
queryRequest baseUrl query =
    { headers = []
    , baseUrl = baseUrl
    , expect = Document.decoder query
    , timeout = Nothing
    , withCredentials = False
    , details = Query Nothing query
    , queryParams = []
    }
        |> Request


{-| Exactly like `queryRequest`, but with an option to use the HTTP GET request
method. You will probably want to use `GetIfShortEnough`, which uses GET if the
full URL ends up being under 2000 characters, or POST otherwise, since [some browsers
don't support URLs over a certain length](https://stackoverflow.com/questions/812925/what-is-the-maximum-possible-length-of-a-query-string?noredirect=1&lq=1).
`GetIfShortEnough` will typically do what you need. If you must use GET no matter
what when hitting your endpoint, you can use `AlwaysGet`.

`queryRequest` always uses POST since some GraphQL API's don't support GET
requests (for example, the Github API assumes that you are doing an introspection
query if you make a GET request). But for semantic reasons, GET requests
are sometimes useful for sending GraphQL Query requests. That is, a GraphQL Query
does not perform side-effects on the server like a Mutation does, so a GET
indicates this and allows some servers to cache requests. See
[this github thread from the Apollo project](https://github.com/apollographql/apollo-client/issues/813)
for more details.

-}
queryRequestWithHttpGet : String -> QueryRequestMethod -> SelectionSet decodesTo RootQuery -> Request decodesTo
queryRequestWithHttpGet baseUrl requestMethod query =
    { headers = []
    , baseUrl = baseUrl
    , expect = Document.decoder query
    , timeout = Nothing
    , withCredentials = False
    , details = Query (Just requestMethod) query
    , queryParams = []
    }
        |> Request


{-| Initialize a basic request from a Mutation. You can add on options with `withHeader`,
`withTimeout`, `withCredentials`, and send it with `Graphqelm.Http.send`.
-}
mutationRequest : String -> SelectionSet decodesTo RootMutation -> Request decodesTo
mutationRequest baseUrl mutationSelectionSet =
    { details = Mutation mutationSelectionSet
    , headers = []
    , baseUrl = baseUrl
    , expect = Document.decoder mutationSelectionSet
    , timeout = Nothing
    , withCredentials = False
    , queryParams = []
    }
        |> Request


{-| Represents the two types of errors you can get, an Http error or a GraphQL error.
See the `Graphqelm.Http.GraphqlError` module docs for more details.
-}
type Error parsedData
    = GraphqlError (GraphqlError.PossiblyParsedData parsedData) (List GraphqlError.GraphqlError)
    | HttpError Http.Error


{-| Map the error data if it is `ParsedData`.
-}
mapError : (a -> b) -> Error a -> Error b
mapError mapFn error =
    case error of
        GraphqlError possiblyParsedData graphqlErrorList ->
            case possiblyParsedData of
                GraphqlError.ParsedData parsedData ->
                    GraphqlError (GraphqlError.ParsedData (mapFn parsedData)) graphqlErrorList

                GraphqlError.UnparsedData unparsedData ->
                    GraphqlError (GraphqlError.UnparsedData unparsedData) graphqlErrorList

        HttpError httpError ->
            HttpError httpError


{-| Useful when you don't want to deal with the recovered data if there is `ParsedData`.
Just a shorthand for `mapError` that will turn any `ParsedData` into `()`.
-}
ignoreParsedErrorData : Error parsedData -> Error ()
ignoreParsedErrorData error =
    mapError (\_ -> ()) error


type alias DataResult parsedData =
    Result ( GraphqlError.PossiblyParsedData parsedData, List GraphqlError.GraphqlError ) parsedData


convertResult : Result Http.Error (DataResult decodesTo) -> Result (Error decodesTo) decodesTo
convertResult httpResult =
    case httpResult of
        Ok successOrError ->
            case successOrError of
                Ok value ->
                    Ok value

                Err ( possiblyParsedData, error ) ->
                    Err (GraphqlError possiblyParsedData error)

        Err httpError ->
            Err (HttpError httpError)


{-| Send the `Graphqelm.Request`
You can use it on its own, or with a library like
[RemoteData](http://package.elm-lang.org/packages/krisajenkins/remotedata/latest/).

    import Graphqelm.Http
    import Graphqelm.OptionalArgument exposing (OptionalArgument(Null, Present))
    import RemoteData exposing (RemoteData)

    type Msg
        = GotResponse RemoteData Graphqelm.Http.Error Response

    makeRequest : Cmd Msg
    makeRequest =
        query
            |> Graphqelm.Http.queryRequest "https://graphqelm.herokuapp.com/"
            |> Graphqelm.Http.withHeader "authorization" "Bearer abcdefgh12345678"
            -- If you're not using remote data, it's just
            -- |> Graphqelm.Http.send GotResponse
            -- With remote data, it's as below
            |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

If any errors are present, you will get a `GraphqlError` that includes the details.
GraphQL allows for partial data to be returned in the case of errors so you can
inspect the data returned in the `GraphqlError` if you would like to try to recover
any data that made it through in the response.

-}
send : (Result (Error decodesTo) decodesTo -> msg) -> Request decodesTo -> Cmd msg
send resultToMessage graphqelmRequest =
    graphqelmRequest
        |> toRequest
        |> Http.send (convertResult >> resultToMessage)


toRequest : Request decodesTo -> Http.Request (DataResult decodesTo)
toRequest (Request request) =
    (case request.details of
        Query forcedRequestMethod querySelectionSet ->
            let
                queryRequestDetails =
                    QueryHelper.build
                        (case forcedRequestMethod of
                            Just AlwaysGet ->
                                Just QueryHelper.Get

                            Just GetIfShortEnough ->
                                Nothing

                            Nothing ->
                                Just QueryHelper.Post
                        )
                        request.baseUrl
                        request.queryParams
                        querySelectionSet
            in
            { method =
                case queryRequestDetails.method of
                    QueryHelper.Get ->
                        "GET"

                    QueryHelper.Post ->
                        "Post"
            , headers = request.headers
            , url = queryRequestDetails.url
            , body = queryRequestDetails.body
            , expect = Http.expectJson (decoderOrError request.expect)
            , timeout = request.timeout
            , withCredentials = request.withCredentials
            }

        Mutation mutationSelectionSet ->
            { method = "POST"
            , headers = request.headers
            , url = request.baseUrl |> QueryParams.urlWithQueryParams request.queryParams
            , body =
                Http.jsonBody
                    (Json.Encode.object
                        [ ( "query"
                          , Json.Encode.string (Document.serializeMutation mutationSelectionSet)
                          )
                        ]
                    )
            , expect = Http.expectJson (decoderOrError request.expect)
            , timeout = request.timeout
            , withCredentials = request.withCredentials
            }
    )
        |> Http.request


{-| Convert a Request to a Task. See `Graphqelm.Http.send` for an example of
how to build up a Request.
-}
toTask : Request decodesTo -> Task (Error decodesTo) decodesTo
toTask request =
    request
        |> toRequest
        |> Http.toTask
        |> Task.mapError HttpError
        |> Task.andThen failTaskOnHttpSuccessWithErrors


failTaskOnHttpSuccessWithErrors : DataResult decodesTo -> Task (Error decodesTo) decodesTo
failTaskOnHttpSuccessWithErrors successOrError =
    case successOrError of
        Ok value ->
            Task.succeed value

        Err ( possiblyParsedData, graphqlErrorGraphqlErrorList ) ->
            Task.fail (GraphqlError possiblyParsedData graphqlErrorGraphqlErrorList)


decoderOrError : Json.Decode.Decoder a -> Json.Decode.Decoder (DataResult a)
decoderOrError decoder =
    Json.Decode.oneOf
        [ errorDecoder decoder
        , decoder |> Json.Decode.map Ok
        ]


errorDecoder : Json.Decode.Decoder a -> Json.Decode.Decoder (DataResult a)
errorDecoder decoder =
    Json.Decode.oneOf
        [ decoder |> Json.Decode.map GraphqlError.ParsedData |> Json.Decode.andThen decodeErrorWithData
        , Json.Decode.field "data" Json.Decode.value |> Json.Decode.map GraphqlError.UnparsedData |> Json.Decode.andThen decodeErrorWithData
        ]


decodeErrorWithData : GraphqlError.PossiblyParsedData a -> Json.Decode.Decoder (DataResult a)
decodeErrorWithData data =
    GraphqlError.decoder |> Json.Decode.map ((,) data) |> Json.Decode.map Err


{-| Add a header.

    makeRequest : Cmd Msg
    makeRequest =
        query
            |> Graphqelm.Http.queryRequest "https://api.github.com/graphql"
            |> Graphqelm.Http.withHeader "authorization" "Bearer <my token>"
            |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

-}
withHeader : String -> String -> Request decodesTo -> Request decodesTo
withHeader key value (Request request) =
    Request { request | headers = Http.header key value :: request.headers }


{-| Add query params. The values will be Uri encoded.

    makeRequest : Cmd Msg
    makeRequest =
        query
            |> Graphqelm.Http.queryRequest "https://api.github.com/graphql"
            |> Graphqelm.Http.withQueryParams [ ( "version", "1.2.3" ) ]
            |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

-}
withQueryParams : List ( String, String ) -> Request decodesTo -> Request decodesTo
withQueryParams additionalQueryParams (Request request) =
    Request { request | queryParams = request.queryParams ++ additionalQueryParams }


{-| Add a timeout.
-}
withTimeout : Time -> Request decodesTo -> Request decodesTo
withTimeout timeout (Request request) =
    Request { request | timeout = Just timeout }


{-| Set with credentials to true.
-}
withCredentials : Request decodesTo -> Request decodesTo
withCredentials (Request request) =
    Request { request | withCredentials = True }