defmodule GraphQL.QueryBuilder do
@moduledoc """
Functions to simplify the creation of GraphQL queries.
The easiest way to use these functions is to `import` this module directly,
this way you'll have all you need to build a query.
## Helper functions
- `query/4` - creates a new "query" operation
- `mutation/4` - creates a new "mutation" operation
- `field/3` - creates a new field (optionals: variables and subfields)
- `fragment/1` - creates a reference to a fragment
- `fragment/3`- creates a fragment
- `inline_fragment/2` - creates an inline fragment
## Writing queries and mutations
As an example, consider the following GraphQL request:
```
query UserQuery($id: Integer = 1) {
user (id: $id) {
id
email
...personFields
}
}
fragment personField on Person {
firstName
lastName
}
```
Using the functions in this module, you can create a representation of this
query in this way:
```
q = query("UserQuery", %{id: {"Integer", 1}}, [
field(:user, %{}, [
field(:id)
field(:email),
fragment("personFields")
])
], [
fragment("personFields", "Person", [
field("firstName"),
field("lastName")
])
])
```
"""
alias GraphQL.{Node, Query, Variable}
@doc """
Creates a new `GraphQL.Query` struct, for a `:query` operation.
"""
@spec query(String.t(), map(), list(), list()) :: Query.t()
def query(name, variables, fields, fragments \\ []) do
build(:query, name, variables, fields, fragments)
end
@doc """
Creates a new `GraphQL.Query` struct, for a `:mutation` operation
"""
@spec mutation(String.t(), map(), list(), list()) :: Query.t()
def mutation(name, variables, fields, fragments \\ []) do
build(:mutation, name, variables, fields, fragments)
end
defp build(operation, name, variables, fields, fragments) do
%Query{
operation: operation,
name: name,
fields: fields,
fragments: fragments,
variables: parse_variables(variables)
}
end
@doc """
Creates a field.
When rendered, it will have the following body:
1. A simple field, no arguments or sub fields
```
fieldName
```
2. A field with an alias
```
fieldAlias: fieldName
```
3. A field with arguments
```
fieldName(arg: value)
```
4. A field with sub fields
```
fieldName {
subField
}
```
5. A field an alias, arguments and sub fields
```
fieldAlias: fieldName (arg: value) {
subField
}
```
## Examples
iex> field(:some_field)
%GraphQL.Node{node_type: :field, name: :some_field}
iex> field({:some_field, "fieldAlias"})
%GraphQL.Node{node_type: :field, name: :some_field, alias: "fieldAlias"}
iex> field("anotherField", %{}, [field(:id)])
%GraphQL.Node{node_type: :field, name: "anotherField", nodes: [%GraphQL.Node{node_type: :field, name: :id}]}
"""
@spec field(Node.name() | Node.name_and_alias(), map(), Keyword.t(Node.t())) :: Node.t()
def field(name, args \\ nil, fields \\ nil, directives \\ nil) do
args = if(args == %{}, do: nil, else: args)
Node.field(name, args, fields, directives)
end
@doc """
Creates a `GraphQL.Variable` struct.
"""
@spec var(any(), any(), any()) :: Variable.t()
def var(name, type, value \\ nil) do
%Variable{name: name, type: type, default_value: value}
end
@spec enum(String.t()) :: {:enum, String.t()}
def enum(name) do
{:enum, name}
end
@doc """
Creates a reference to a fragment. Use it inside a field.
When rendered, it will generate the following body:
```
...fragmentName
```
## Examples
iex> fragment(:fields)
%GraphQL.Node{node_type: :fragment_ref, name: :fields}
"""
@spec fragment(String.t()) :: Node.t()
def fragment(name) do
Node.fragment(name)
end
@doc """
Creates a fragment. Use it on the query level.
When rendered, it will generate the following body:
```
... fragmentName on Type {
field1
field2
}
```
## Examples
iex> fragment("personFields", "Person", [field(:name)])
%GraphQL.Node{node_type: :fragment, name: "personFields", type: "Person", nodes: [%GraphQL.Node{node_type: :field, name: :name}]}
"""
@spec fragment(String.t(), String.t(), list()) :: Node.t()
def fragment(name, type, fields) do
Node.fragment(name, type, fields)
end
@doc """
Creates an inline fragment. Use it inside a field.
When rendered, it will generate the following body:
```
... on Type {
field1
field2
}
```
## Examples
iex> inline_fragment("Person", [field(:name)])
%GraphQL.Node{node_type: :inline_fragment, type: "Person", nodes: [%GraphQL.Node{node_type: :field, name: :name}]}
"""
@spec inline_fragment(String.t(), list()) :: Node.t()
def inline_fragment(type, fields) do
Node.inline_fragment(type, fields)
end
# Variables
defp parse_variables(vars) do
Enum.map(vars, &parse_variable/1)
end
defp parse_variable({name, {type, default}}) do
%Variable{name: name, type: type, default_value: default}
end
defp parse_variable({name, type}) do
%Variable{name: name, type: type, default_value: nil}
end
end