jamesmacaulay / elm-graphql / GraphQL.Request.Builder

This module provides an interface for building up GraphQL requests in a way that gives you everything you need to safely and conveniently integrate them with your Elm program:

In order to use arguments and variables in your requests, you will need to use functions from the GraphQL.Request.Builder.Value and GraphQL.Request.Builder.Variable modules. To send your requests over HTTP, see the GraphQL.Client.Http module.

Specifying the structure of values


type ValueSpec nullability coreType result vars

A ValueSpec is a structured way of describing a value that you want back from a GraphQL server, and it is the fundamental building block of the request builder interface provided by this module. It corresponds loosely with the GraphQL concept of the "selection set", but it is used for scalar values as well as object values, and holds more information about their expected types.

The nullability and coreType parameters are used by various functions in this module to ensure consistency when combining ValueSpec values. As such, they will probably only become relevant to you when reading error messages from the compiler, at which point they will hopefully make the situation easier to understand.

The result parameter specifies the type produced by the JSON decoder of the ValueSpec.

The vars parameter specifies the type of the Elm value required to supply any variables used anywhere within the ValueSpec.


type NonNull

Indicates that a ValueSpec describes GraphQL values that may not be null. Unlike in the GraphQL schema language, NonNull is the default in this library.


type Nullable

Indicates that a ValueSpec describes GraphQL values that may be null.


type IntType

Indicates that a ValueSpec describes GraphQL Int values.


type FloatType

Indicates that a ValueSpec describes GraphQL Float values.


type StringType

Indicates that a ValueSpec describes GraphQL String values.


type BooleanType

Indicates that a ValueSpec describes GraphQL Boolean values.


type IdType

Indicates that a ValueSpec describes GraphQL ID values.


type EnumType

Indicates that a ValueSpec describes values of some GraphQL Enum type.


type ListType itemNullability itemCoreType

Indicates that a ValueSpec describes values of some GraphQL List type.


type ObjectType

Indicates that a ValueSpec describes values of some GraphQL Object type.

Objects and selections

object : (fieldValue -> a) -> ValueSpec NonNull ObjectType (fieldValue -> a) vars

Takes a constructor function for an Elm type you want to produce, and returns a ValueSpec for an object without any fields yet specified. This function is intended to start a pipeline of calls to the with function to add field and fragment selections to the ValueSpec. The order of arguments to the constructor function determines the order that the selections must be added. For example:

type alias User =
    { name : String
    , isAdmin : Bool
    }

userSummary : ValueSpec NonNull ObjectType User vars
userSummary =
    object User
        |> with (field "name" [] string)
        |> with (field "isAdmin" [] bool)

The above ValueSpec produces a GraphQL selection set that looks like the following:

{
  name
  isAdmin
}

The same ValueSpec also provides a JSON decoder for decoding part of the response, equivalent to the following:

Json.Decode.map2 User
    (Json.Decode.field "name" Json.Decode.string)
    (Json.Decode.field "isAdmin" Json.Decode.bool)


type SelectionSpec selectionType result vars

A specification for a GraphQL selection, to be added to an object ValueSpec using the with function.

The selectionType can be either Field, FragmentSpread, or InlineFragment.

The result parameter specifies the type produced by the JSON decoder of the SelectionSpec.

The vars parameter specifies the type of the Elm value required to supply any variables used anywhere within the SelectionSpec.

with : SelectionSpec selectionType a vars -> ValueSpec NonNull ObjectType (a -> b) vars -> ValueSpec NonNull ObjectType b vars

Use this function to add SelectionSpecs to an object ValueSpec pipeline:

type alias User =
    { name : String
    , adminAccess : Bool
    }

userSpec : ValueSpec NonNull ObjectType User vars
userSpec =
    object User
        |> with (field "name" [] string)
        |> with (field "adminAccess" [] bool)

withLocalConstant : a -> ValueSpec NonNull ObjectType (a -> b) vars -> ValueSpec NonNull ObjectType b vars

Adds a hardcoded local constant value to an object ValueSpec pipeline. This can be useful for initializing records with default local state values:

type alias Item =
    { name : String
    , selected : Bool
    }

itemSpec : ValueSpec NonNull ObjectType Item vars
itemSpec =
    object Item
        |> with (field "name" [] string)
        |> withLocalConstant False

Any Item record decoded by itemSpec would then have its selected field initialized to False. Adding a local constant in this way has no effect on the corresponding GraphQL selection set that is sent to the server — itemSpec's selection set would simply be { name }.

extract : SelectionSpec selectionType result vars -> ValueSpec NonNull ObjectType result vars

Make a ValueSpec for an object with only a single SelectionSpec. In cases where you only need one selection from an object, this function lets you conveniently extract the decoded result of that selection directly into the parent result. For example, the following code uses extract to hoist the id field of each Project result into a list of projectIDs on the User record:

type alias User =
    { name : String
    , projectIDs : List String
    }

userSpec : ValueSpec NonNull ObjectType User vars
userSpec =
    object User
        |> with (field "name" [])
        |> with
            (field "projects"
                []
                (list (extract (field "id" [] id)))
            )

This helps you avoid having extra levels of nesting that you don't need in your result types.

assume : SelectionSpec selectionType (Maybe result) vars -> SelectionSpec selectionType result vars

Convert a SelectionSpec that decodes to a Maybe type into one that assumes the presence of a value and unwraps the Just wrapper for you. If Nothing is encountered, then the entire decoding process of the response will fail, so don't use this unless you are confident in your assumption! This function is most useful when you know that a fragment spread or inline fragment will successfully match on an object and don't want to deal with an unnecessary Maybe wrapper:

type alias User =
    { id : String
    , name : String
    }

userNameFragment : Fragment (Maybe String) vars
userNameFragment =
    fragment "userNameFragment"
        (onType "User")
        (extract (field "name" [] string))

userSpec : ValueSpec NonNull ObjectType User vars
userSpec =
    object User
        |> with (field "id" [] id)
        |> with (assume (fragmentSpread userNameFragment))

As long as the above userSpec is only ever used for selection sets on the schema's "User" type, then the fragment should always be returned by the server and the assume will always succeed.

Depending on the semantics of the GraphQL schema you're working with, it may also be safe to use in some cases where fields are nullable in the schema but you know that in certain cases they are predictably non-null.

withDirectives : List ( String, List ( String, Arg.Value vars ) ) -> SelectionSpec selectionType result vars -> SelectionSpec selectionType (Maybe result) vars

Specify a list of directives to use with a GraphQL selection. Each directive takes the form of a tuple of (directiveName, args). The returned SelectionSpec's decoder wraps the result type in a Maybe to account for the fact that the server may omit the selection because of the directives.

keyValuePairs : List (SelectionSpec Field value vars) -> ValueSpec NonNull ObjectType (List ( String, value )) vars

Takes a list of field selections, all of whom must decode to the same Elm type, and returns a ValueSpec for an object that decodes to a list of corresponding (key, value) pairs. Especially useful when you want to dynamically query multiple variations of the same field using aliases. For example:

userAvatarUrlField : String -> SelectionSpec Field String vars
userAvatarUrlField name =
    aliasAs name <|
        field "user"
            [ ( "login", Arg.string name ) ]
            (extract (field "avatarUrl" [] string))

userAvatarUrls : List String -> Document Query (List ( String, String )) vars
userAvatarUrls names =
    queryDocument <|
        keyValuePairs (List.map userAvatarUrlField names)

If you used this code to construct a query document with userAvatarUrls ["alice", "bob"], the resulting query would look like this:

alice: user(login: "alice") {
  avatarUrl
}
bob: user(login: "bob") {
  avatarUrl
}

...and a successful decoded result would look something like this:

[ ("alice", "https://cdn.example.com/alice.png")
, ("bob", "https://cdn.example.com/bob.png")
]

Note that field aliases must still conform to the GraphQL spec:

https://facebook.github.io/graphql/#sec-Names

This means that the above example would not be suitable to use when the usernames are supplied from user input. If the user supplies a name that is not a valid GraphQL alias, then the GraphQL server would return an error response. In a case like that where you are generating a query from user input, you will need to find some other way of generating the field aliases.

dict : List (SelectionSpec Field value vars) -> ValueSpec NonNull ObjectType (Dict String value) vars

Just like keyValuePairs, but the decoded result is a Dict String value constructed by applying Dict.fromList to the decoded result of keyValuePairs.

Fields


type Field

Indicates that a SelectionSpec represents a GraphQL field.

field : String -> List ( String, Arg.Value vars ) -> ValueSpec nullability coreType result vars -> SelectionSpec Field result vars

Constructs a SelectionSpec for a field that you can add to an object ValueSpec with the with function. Takes the name of the field, a list of arguments, and a ValueSpec for the field's value.

aliasAs : String -> SelectionSpec Field result vars -> SelectionSpec Field result vars

Give an alias to a field, overriding its response key. Useful when you need to ask for a field multiple times with different sets of arguments:

type alias QueryRoot =
    { username1 : String
    , username2 : String
    }

querySpec : ValueSpec NonNull ObjectType QueryRoot vars
querySpec =
    object QueryRoot
        |> with
            (aliasAs "user1"
                (field "user"
                    [ ( "id", Arg.id "1" ) ]
                    (extract (field "name" [] string))
                )
            )
        |> with
            (aliasAs "user2"
                (field "user"
                    [ ( "id", Arg.id "2" ) ]
                    (extract (field "name" [] string))
                )
            )

Fragments


type Fragment result vars

A fragment definition. The vars parameter specifies the type of the Elm value required to supply any variables used anywhere within the fragment. The result parameter is the type of the Elm value obtained from the Fragment's JSON decoder.


type FragmentSpread

Indicates that a SelectionSpec represents a GraphQL fragment spread.


type InlineFragment

Indicates that a SelectionSpec represents a GraphQL inline fragment.


type alias TypeCondition =
GraphQL.Request.Document.AST.TypeCondition

Specifies a named object, interface, or union type from the GraphQL schema that a fragment or inline fragment is valid to be used with.

fragment : String -> TypeCondition -> ValueSpec NonNull ObjectType result vars -> Fragment result vars

Construct a Fragment by providing a name, a TypeCondition, and a ValueSpec. The ValueSpec argument must be a NonNull Object ValueSpec, because it represents the selection set of the fragment.

onType : String -> TypeCondition

Construct a TypeCondition from the name of an object, interface, or union type defined in a GraphQL schema.

fragmentSpread : Fragment result vars -> SelectionSpec FragmentSpread (Maybe result) vars

Constructs a SelectionSpec for a fragment spread that you can add to an object ValueSpec with the with function. Takes a Fragment and a list of optional directives. The directives are tuples whose first element is the name of the directive, and whose second element is a list of key-value tuples representing the directive arguments. Argument values are constructed using functions from GraphQL.Request.Builder.Value.

The fragment decoder's result type is wrapped in a Maybe to account for fragments with type constraints that do not hold for all values of the parent ValueSpec. This means that normally the parent ValueSpec's constructor function must accept a Maybe of the fragment result as its next argument:

type alias User =
    { name : String
    , employeeInfo : Maybe EmployeeInfo
    }

type alias EmployeeInfo =
    { employeeNumber : Int
    , title : String
    }

employeeInfoFragment : Fragment EmployeeInfo vars
employeeInfoFragment =
    fragment "employeeInfoFragment"
        (onType "Employee")
        (object EmployeeInfo
            |> with (field "employeeNumber" [] int)
            |> with (field "title" [] string)
        )

userSpec : ValueSpec NonNull ObjectType User vars
userSpec =
    object User
        |> with (field "name" [] string)
        |> with (fragmentSpread employeeInfoFragment [])

In cases where you know for sure that a fragment will successfully produce values in the response for a given ValueSpec, you can use the assume function to unwrap the Maybe for you.

Including the above userSpec anywhere in a Document results in the following fragment definition being included in the serialized output:

fragment employeeInfoFragment on Employee {
  employeeNumber
  title
}

Meanwhile, the selection set of userSpec itself would look like this wherever it's used:

{
  name
  ...employeeInfoFragment
}

inlineFragment : Maybe TypeCondition -> ValueSpec NonNull ObjectType result vars -> SelectionSpec InlineFragment (Maybe result) vars

Constructs a SelectionSpec for an object with a single inline fragment. Takes an optional TypeCondition, a list of optional directives, and a ValueSpec representing the selection set of the inline fragment. The directives are tuples whose first element is the name of the directive, and whose second element is a list of key-value tuples representing the directive arguments. Argument values are constructed using functions from GraphQL.Request.Builder.Value.

The result type of the inline fragment's SelectionSpec is wrapped in a Maybe to account for type constraints that do not hold for all values of the parent ValueSpec. This means that the parent ValueSpec's constructor function must accept a Maybe of the fragment result as its next argument:

type alias User =
    { name : String
    , employeeInfo : Maybe EmployeeInfo
    }

type alias EmployeeInfo =
    { employeeNumber : Int
    , title : String
    }

userSpec : ValueSpec NonNull ObjectType User vars
userSpec =
    object User
        |> with (field "name" [] string)
        |> with
            (inlineFragment (Just (onType "Employee"))
                []
                (object EmployeeInfo
                    |> with (field "employeeNumber" [] int)
                    |> with (field "title" [] string)
                )
            )

The selection set of the above userSpec would look like the following wherever it's used:

{
  name
  ... on Employee {
    employeeNumber
    title
  }
}

Scalars

int : ValueSpec NonNull IntType Basics.Int vars

A ValueSpec for the GraphQL Int type that decodes to an Elm Int.

float : ValueSpec NonNull FloatType Basics.Float vars

A ValueSpec for the GraphQL Float type that decodes to an Elm Float.

string : ValueSpec NonNull StringType String vars

A ValueSpec for the GraphQL String type that decodes to an Elm String.

bool : ValueSpec NonNull BooleanType Basics.Bool vars

A ValueSpec for the GraphQL Boolean type that decodes to an Elm Bool.

id : ValueSpec NonNull IdType String vars

A ValueSpec for the GraphQL ID type that decodes to an Elm String.

enum : List ( String, result ) -> ValueSpec NonNull EnumType result vars

Constructs a ValueSpec for a GraphQL Enum type. Takes a list of string-result pairs to map Enum values to result values. For example:

type AccessLevel
    = AdminAccess
    | MemberAccess

userAccessLevel : ValueSpec NonNull EnumType AccessLevel vars
userAccessLevel =
    enum
        [ ( "ADMIN", AdminAccess )
        , ( "MEMBER", MemberAccess )
        ]

enumWithDefault : (String -> result) -> List ( String, result ) -> ValueSpec NonNull EnumType result vars

Constructs a ValueSpec for a GraphQL Enum type. Works the same as enum, but takes a default function to produce a result value from any Enum value not specified in the list of known Enum values. This is useful if you expect a schema to add more possible values to an Enum type in the future and don't want to bail out on the decoding process every time you encounter something you haven't seen before:

type AccessLevel
    = AdminAccess
    | MemberAccess
    | UnknownAccess String

userAccessLevel : ValueSpec NonNull EnumType AccessLevel vars
userAccessLevel =
    enumWithDefault UnknownAccess
        [ ( "ADMIN", AdminAccess )
        , ( "MEMBER", MemberAccess )
        ]

customScalar : customTypeMarker -> Json.Decode.Decoder result -> ValueSpec NonNull customTypeMarker result vars

Create a ValueSpec for a custom scalar type defined in the GraphQL schema you're interacting with. The customTypeMarker is an Elm type that you define specifically for this custom scalar ValueSpec you're making. The type should have a single constructor that takes no arguments, and both should have the CamelCase name of the custom scalar type in the GraphQL schema, plus a "Type" suffix. For example, if your GraphQL schema has a Time type, you should define your customTypeMarker like the following, in some module of your choosing:

type TimeType
    = TimeType

The type marker is used by this package to help make sure ValueSpecs are only combined in valid ways. In the future, it may be used to help you validate that your queries and mutations against a target schema in your unit tests.

Once you have TimeType to use as a type marker, you can define a ValueSpec for the Time GraphQL type by supplying TimeType as a runtime argument to the function along with a JSON decoder that works with values of the type. For example, you might decide to use the elm-iso8601 package for parsing and define the ValueSpec like so:

import ISO8601
import Json.Decode as Decode

type TimeType
    = TimeType

time : ValueSpec NonNull TimeType ISO8601.Time vars
time =
    Decode.string
        |> Decode.andThen
            (\timeString ->
                case ISO8601.fromString timeString of
                    Ok time ->
                        Decode.succeed time

                    Err errorMessage ->
                        Decode.fail errorMessage
            )
        |> customScalar TimeType

Nullability

nullable : ValueSpec NonNull coreType result vars -> ValueSpec Nullable coreType (Maybe result) vars

Transforms a NonNull ValueSpec into one that allows null values, using a Maybe of the original ValueSpec's result type to represent the nullability in the decoded Elm value.

Note that the default nullability of a ValueSpec in this module is NonNull. This is the opposite of the situation in the GraphQL schema language, whose types must be annotated with the Non-Null (!) modifier in order to specify that their values will never be null.

Lists

list : ValueSpec itemNullability itemType result vars -> ValueSpec NonNull (ListType itemNullability itemType) (List result) vars

Constructs a ValueSpec for a GraphQL List type. Takes any kind of ValueSpec to use for the items of the list, and returns a ValueSpec that decodes into an Elm List.

Customizing the decoding process

map : (a -> b) -> ValueSpec nullability coreType a vars -> ValueSpec nullability coreType b vars

Transform the decoded value of a ValueSpec with the given function, just like Json.Decode.map. Here it is used to wrap an id field in a custom type:

type UserId
    = UserId String

type alias User =
    { id : UserId
    , name : String
    }

user : ValueSpec NonNull ObjectType User vars
user =
    object User
        |> with (field "id" [] (map UserId id))
        |> with (field "name" [] string)

Here's an example of using map with nullable to implement a function that can provide a default value for nullable fields:

nullableWithDefault :
    a
    -> ValueSpec NonNull coreType a vars
    -> ValueSpec Nullable coreType a vars
nullableWithDefault default spec =
    map (Maybe.withDefault default) (nullable spec)

Documents


type Document operationType result vars

A Document represents a single-operation GraphQL request document, along with the information necessary to encode variable values used in the document and to decode successful responses from the server. The vars parameter is the type of value that must be provided when constructing a Request in order for the document's variable values to be obtained. The operationType and result parameters are the same as in the Request type.


type Query

This type is used as a marker for Request and Document types to indicate that the document's operation is a query.

queryDocument : ValueSpec NonNull ObjectType result vars -> Document Query result vars

Take a ValueSpec and return a Document for a single query operation. The argument must be a NonNull Object ValueSpec, because it represents the root-level selection set of the query operation.

namedQueryDocument : String -> ValueSpec NonNull ObjectType result vars -> Document Query result vars

Like queryDocument, but takes a name for the query as an extra first argument. The name is used as a label for the operation in the generated GraphQL document and can be useful for logging and debugging purposes.


type Mutation

This type is used as a marker for Request and Document types to indicate that the document's operation is a mutation.

mutationDocument : ValueSpec NonNull ObjectType result vars -> Document Mutation result vars

Take a ValueSpec and return a Document for a single mutation operation. The argument must be a NonNull Object ValueSpec, because it represents the root-level selection set of the mutation operation. Here's an example of a mutation that logs in a user and extracts an auth token from the response:

type alias LoginVars =
    { username : String
    , password : String
    }

loginMutation : Document Mutation String LoginVars
loginMutation =
    let
        usernameVar =
            Var.required "username" .username Var.string

        passwordVar =
            Var.required "password" .password Var.string
    in
        mutationDocument <|
            extract
                (field "login"
                    [ ( "username", Arg.variable usernameVar )
                    , ( "password", Arg.variable passwordVar )
                    ]
                    (extract (field "token" [] string))
                )

namedMutationDocument : String -> ValueSpec NonNull ObjectType result vars -> Document Mutation result vars

Like mutationDocument, but takes a name for the mutation as an extra first argument. The name is used as a label for the operation in the generated GraphQL document and can be useful for logging and debugging purposes.

Requests


type Request operationType result

A Request bundles a Document along with any variable values that are to be sent along with it to the server. The operationType parameter may be either Query or Mutation. The result parameter is the type that a successful response from the server is decoded into.

request : vars -> Document operationType result vars -> Request operationType result

Turn a Document into a Request that can be sent to a server, by supplying a vars value that is used to obtain values for any variables used in the Document. If the Document does not use any variables, then you can pass in () or any other value as the vars and it will be ignored.

requestBody : Request operationType result -> String

Get the serialized document body of a Request.

jsonVariableValues : Request operationType result -> Maybe Json.Encode.Value

Get the variable values associated with a Request (if there are any) as a Maybe Json.Encode.Value, ready to be sent as a parameter to a GraphQL server.

responseDataDecoder : Request operationType result -> Json.Decode.Decoder result

Get a JSON decoder that can be used to decode the data contained in a successful response to a Request. If you're working with a conventional GraphQL response over HTTP, the returned Decoder works on the data found under the "data" key of the response.