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:
Json.Decode.Decoder
value that is built up as you build each part of the request document.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.
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
.
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.
Indicates that a ValueSpec
describes GraphQL values that may be null
.
Indicates that a ValueSpec
describes GraphQL Int
values.
Indicates that a ValueSpec
describes GraphQL Float
values.
Indicates that a ValueSpec
describes GraphQL String
values.
Indicates that a ValueSpec
describes GraphQL Boolean
values.
Indicates that a ValueSpec
describes GraphQL ID
values.
Indicates that a ValueSpec
describes values of some GraphQL Enum type.
Indicates that a ValueSpec
describes values of some GraphQL List type.
Indicates that a ValueSpec
describes values of some GraphQL Object type.
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)
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 SelectionSpec
s 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
.
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))
)
)
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.
Indicates that a SelectionSpec
represents a GraphQL fragment spread.
Indicates that a SelectionSpec
represents a GraphQL inline fragment.
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
}
}
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 ValueSpec
s 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
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
.
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
.
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)
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.
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.
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.
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.