Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Executor refactoring #82

Merged
36 changes: 28 additions & 8 deletions lib/graphql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,34 @@ defmodule GraphQL do
alias GraphQL.Validation.Validator
alias GraphQL.Execution.Executor

@doc """
Execute a query against a schema (with validation)

# iex> GraphQL.execute_with_opts(schema, "{ hello }")
# {:ok, %{hello: world}}
"""
# FIXME: when the execute/5 form is removed (after updating the plug)
# then rename this to `execute`.
def execute_with_opts(schema, query, opts) do
execute_with_optional_validation(true, schema, query, opts)
end

@doc """
Execute a query against a schema (with validation)

# iex> GraphQL.execute(schema, "{ hello }")
# {:ok, %{hello: world}}
"""
# TODO: delete this when a new plug is released.
def execute(schema, query, root_value \\ %{}, variable_values \\ %{}, operation_name \\ nil) do
execute_with_optional_validation(true, schema, query, root_value, variable_values, operation_name)
execute_with_optional_validation(
true,
schema,
query,
root_value: root_value,
variable_values: variable_values,
operation_name: operation_name
)
end

@doc """
Expand All @@ -32,19 +52,19 @@ defmodule GraphQL do
# iex> GraphQL.execute(schema, "{ hello }")
# {:ok, %{hello: world}}
"""
def execute_without_validation(schema, query, root_value \\ %{}, variable_values \\ %{}, operation_name \\ nil) do
execute_with_optional_validation(false, schema, query, root_value, variable_values, operation_name)
def execute_without_validation(schema, query, opts) do
execute_with_optional_validation(false, schema, query, opts)
end

defp execute_with_optional_validation(should_validate, schema, query, root_value, variable_values, operation_name) do
# NOTE: it would be nice if we could compose functions together in a chain (with ot without validation step).
# See: http://www.zohaib.me/railway-programming-pattern-in-elixir/
defp execute_with_optional_validation(should_validate, schema, query, opts) do
# TODO: use the `with` statement to compose write this in a nicer way
case GraphQL.Lang.Parser.parse(query) do
{:ok, document} ->
case optionally_validate(should_validate, schema, document) do
:ok ->
case Executor.execute(schema, document, root_value, variable_values, operation_name) do
{:ok, response} -> {:ok, %{data: response}}
case Executor.execute(schema, document, opts) do
{:ok, data, []} -> {:ok, %{data: data}}
{:ok, data, errors} -> {:ok, %{data: data, errors: errors}}
{:error, errors} -> {:error, errors}
end
{:error, errors} ->
Expand Down
46 changes: 46 additions & 0 deletions lib/graphql/execution/execution_context.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

defmodule GraphQL.Execution.ExecutionContext do

defstruct [:schema, :fragments, :root_value, :operation, :variable_values, :errors]
@type t :: %__MODULE__{
schema: GraphQL.Schema.t,
fragments: struct,
root_value: Map,
operation: Map,
variable_values: Map,
errors: list(GraphQL.Error.t)
}

@spec new(GraphQL.Schema.t, GraphQL.Document.t, map, map, String.t) :: __MODULE__.t
def new(schema, document, root_value, variable_values, operation_name) do
Enum.reduce document.definitions, %__MODULE__{
schema: schema,
fragments: %{},
root_value: root_value,
operation: nil,
variable_values: variable_values || %{}, # TODO: We need to deeply set keys as strings or atoms. not allow both.
errors: []
}, fn(definition, context) ->

case definition do
%{kind: :OperationDefinition} ->
cond do
!operation_name && context.operation ->
report_error(context, "Must provide operation name if query contains multiple operations.")
!operation_name || definition.name.value === operation_name ->
context = %{context | operation: definition}
%{context | variable_values: GraphQL.Execution.Variables.extract(context) }
true -> context
end
%{kind: :FragmentDefinition} ->
put_in(context.fragments[definition.name.value], definition)
end
end
end

@spec report_error(__MODULE__.t, String.t) :: __MODULE__.t
def report_error(context, msg) do
put_in(context.errors, [%{"message" => msg} | context.errors])
end
end

Loading