defmodule GraphQL.QueryRegistry do
@moduledoc """
Functions to handle query registries.
A query registry stores several `GraphQL.Query` structs, so they
can be combined into a single query before the execution.
"""
alias GraphQL.{Client, Query}
@enforce_keys [:name]
defstruct name: nil, queries: [], variables: [], resolvers: []
@typedoc """
A resolver is a function that must accept two arguments:
- a `GraphQL.Response` struct
- an accumulator, that can be of any type
It also must return the updated value of the accumulator.
"""
@type resolver :: (Response.t(), any() -> any())
@typedoc """
A struct that keeps the information about several queries, variables and
resolvers.
The `name` field will be used as the name of the final query or mutation.
The `queries` field is a list of `GraphQL.Query` structs, that
will be merged before execution.
The `variables` is a map with all _values_ of variables that will be sent
to the server along with the GraphQL body.
The `resolver` is a list of `t:resolver()` functions that can be used to
produce the side effects in an accumulator.
"""
@type t :: %__MODULE__{
name: String.t(),
queries: [Query.t()],
variables: [map()],
resolvers: list()
}
@doc """
Creates a new QueryRegistry struct with the given name.
"""
@spec new(String.t()) :: t()
def new(name) do
%__MODULE__{name: name}
end
@doc """
Add a query to the a query registry
"""
@spec add_query(t(), Query.t(), map()) :: t()
def add_query(%__MODULE__{} = registry, %Query{} = query, variables \\ nil) do
updated_variables =
if variables == %{} || variables == nil do
registry.variables
else
[variables | registry.variables]
end
%__MODULE__{registry | queries: [query | registry.queries], variables: updated_variables}
end
@doc """
Add a new resolver into a query registry
"""
@spec add_resolver(t(), resolver()) :: t()
def add_resolver(%__MODULE__{} = registry, function) when is_function(function, 2) do
add_resolvers(registry, [function])
end
@doc """
Add a list of resolvers into a query registry
"""
@spec add_resolvers(t(), [resolver()]) :: t()
def add_resolvers(%__MODULE__{} = registry, resolvers) do
%__MODULE__{registry | resolvers: registry.resolvers ++ resolvers}
end
@doc """
Executes the given query registry, using the given accumulator `acc` and the given options
"""
@spec execute(t(), any(), Keyword.t()) :: any()
def execute(registry, acc, options \\ []) do
case prepare_query(registry) do
{:ok, {query, variables, resolvers}} ->
result =
query
|> Client.execute(variables, options)
|> resolve(resolvers, acc)
{:ok, result}
error ->
error
end
end
defp prepare_query(%__MODULE__{} = registry) do
case registry.queries do
[] ->
{:error, "no queries available"}
_not_empty ->
case Query.merge_many(registry.queries, registry.name) do
{:ok, query} ->
variables = merge_variables(registry.variables)
{:ok, {query, variables, registry.resolvers}}
error ->
error
end
end
end
defp merge_variables([]), do: %{}
defp merge_variables(variables) do
Enum.reduce(variables, &Map.merge/2)
end
defp resolve(response, resolvers, initial_acc) do
Enum.reduce(resolvers, initial_acc, fn resolver, acc ->
resolver.(response, acc)
end)
end
end