Packages
absinthe
1.4.7
1.11.0
1.10.2
1.10.1
1.10.0
1.9.1
1.9.0
1.8.0
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.8
1.6.7
retired
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.5
1.5.0-rc.4
1.5.0-rc.3
1.5.0-rc.2
1.5.0-rc.1
1.5.0-rc.0
1.5.0-beta.2
1.5.0-beta.1
1.5.0-beta.0
1.5.0-alpha.4
1.5.0-alpha.3
1.5.0-alpha.2
1.5.0-alpha.1
1.5.0-alpha.0
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
retired
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.4.0-beta.5
1.4.0-beta.4
1.4.0-beta.3
1.4.0-beta.2
1.4.0-beta.1
1.3.2
1.3.1
1.3.0
1.3.0-rc.0
1.3.0-beta.2
1.3.0-beta.1
1.3.0-beta.0
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.0
1.2.0-beta.0
1.2.0-alpha0
1.2.0-alpha.2
1.2.0-alpha.1
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
0.5.2
0.5.1
0.5.0
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.2.3
0.2.2
0.2.1
0.1.0
GraphQL for Elixir
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/absinthe/phase/document/execution/resolution.ex
defmodule Absinthe.Phase.Document.Execution.Resolution do
@moduledoc false
# Runs resolution functions in a blueprint.
#
# Blueprint results are placed under `blueprint.result.execution`. This is
# because the results form basically a new tree from the original blueprint.
alias Absinthe.{Blueprint, Type, Phase}
alias Blueprint.{Result, Execution}
alias Absinthe.Phase
use Absinthe.Phase
@spec run(Blueprint.t, Keyword.t) :: Phase.result_t
def run(bp_root, options \\ []) do
case Blueprint.current_operation(bp_root) do
nil -> {:ok, bp_root}
op -> resolve_current(bp_root, op, options)
end
end
defp resolve_current(bp_root, operation, options) do
execution = perform_resolution(bp_root, operation, options)
blueprint = %{bp_root | execution: execution}
if Keyword.get(options, :plugin_callbacks, true) do
bp_root.schema.plugins()
|> Absinthe.Plugin.pipeline(execution)
|> case do
[] ->
{:ok, blueprint}
pipeline ->
{:insert, blueprint, pipeline}
end
else
{:ok, blueprint}
end
end
defp perform_resolution(bp_root, operation, options) do
exec = Execution.get(bp_root, operation)
plugins = bp_root.schema.plugins()
run_callbacks? = Keyword.get(options, :plugin_callbacks, true)
exec = plugins |> run_callbacks(:before_resolution, exec, run_callbacks?)
{result, exec} =
exec.result
|> walk_result(operation, operation.schema_node, exec, [operation])
|> propagate_null_trimming
exec = plugins |> run_callbacks(:after_resolution, exec, run_callbacks?)
%{exec | result: result}
end
defp run_callbacks(plugins, callback, acc, true) do
Enum.reduce(plugins, acc, &apply(&1, callback, [&2]))
end
defp run_callbacks(_, _, acc, _ ), do: acc
@doc """
This function walks through any existing results. If no results are found at a
given node, it will call the requisite function to expand and build those results
"""
def walk_result(%{fields: nil} = result, bp_node, _schema_type, exec, path) do
{fields, exec} = resolve_fields(bp_node, exec, result.root_value, path)
{%{result | fields: fields}, exec}
end
def walk_result(%{fields: fields} = result, bp_node, schema_type, exec, path) do
{fields, exec} = walk_results(fields, bp_node, schema_type, exec, [0 | path], [])
{%{result | fields: fields}, exec}
end
def walk_result(%Result.Leaf{} = result, _, _, exec, _) do
{result, exec}
end
def walk_result(%{values: values} = result, bp_node, schema_type, exec, path) do
{values, exec} = walk_results(values, bp_node, schema_type, exec, [0 | path], [])
{%{result | values: values}, exec}
end
def walk_result(%Absinthe.Resolution{} = res, _bp_node, _schema_type, exec, _path) do
res = update_persisted_fields(res, exec)
do_resolve_field(res, exec, res.source, res.path)
end
# walk list results
defp walk_results([value | values], bp_node, inner_type, exec, [i | sub_path] = path, acc) do
{result, exec} = walk_result(value, bp_node, inner_type, exec, path)
walk_results(values, bp_node, inner_type, exec, [i + 1 | sub_path], [result | acc])
end
defp walk_results([], _, _, exec, _, acc), do: {:lists.reverse(acc), exec}
defp resolve_fields(parent, exec, source, path) do
parent
# parent is the parent field, we need to get the return type of that field
|> get_return_type
# that return type could be an interface or union, so let's make it concrete
|> get_concrete_type(source, exec)
|> case do
nil ->
{[], exec}
parent_type ->
{fields, fields_cache} = Absinthe.Resolution.Projector.project(parent.selections, parent_type, path, exec.fields_cache, exec)
exec = %{exec | fields_cache: fields_cache}
do_resolve_fields(fields, exec, source, parent_type, path, [])
end
end
defp get_return_type(%{schema_node: %Type.Field{type: type}}) do
Type.unwrap(type)
end
defp get_return_type(%{schema_node: schema_node}) do
Type.unwrap(schema_node)
end
defp get_return_type(type), do: type
defp get_concrete_type(%Type.Union{} = parent_type, source, exec) do
Type.Union.resolve_type(parent_type, source, exec)
end
defp get_concrete_type(%Type.Interface{} = parent_type, source, exec) do
Type.Interface.resolve_type(parent_type, source, exec)
end
defp get_concrete_type(parent_type, _source, _exec) do
parent_type
end
defp do_resolve_fields([field | fields], exec, source, parent_type, path, acc) do
{result, exec} = resolve_field(field, exec, source, parent_type, [field | path])
do_resolve_fields(fields, exec, source, parent_type, path, [result | acc])
end
defp do_resolve_fields([], exec, _, _, _, acc), do: {:lists.reverse(acc), exec}
def resolve_field(field, exec, source, parent_type, path) do
exec
|> build_resolution_struct(field, source, parent_type, path)
|> do_resolve_field(exec, source, path)
end
# bp_field needs to have a concrete schema node, AKA no unions or interfaces
defp do_resolve_field(res, exec, source, path) do
res
|> reduce_resolution
|> case do
%{state: :resolved} = res ->
exec = update_persisted_fields(exec, res)
build_result(res, exec, source, path)
%{state: :suspended} = res ->
exec = update_persisted_fields(exec, res)
{res, exec}
final_res ->
raise """
Should have halted or suspended middleware
Started with: #{inspect res}
Ended with: #{inspect final_res}
"""
end
end
defp update_persisted_fields(dest, %{acc: acc, context: context, fields_cache: cache}) do
%{dest | acc: acc, context: context, fields_cache: cache}
end
defp build_resolution_struct(exec, bp_field, source, parent_type, path) do
common = Map.take(exec, [:adapter, :context, :acc, :root_value, :schema, :fragments, :fields_cache])
%Absinthe.Resolution{
path: path,
source: source,
parent_type: parent_type,
middleware: bp_field.schema_node.middleware,
definition: bp_field,
arguments: bp_field.argument_data,
} |> Map.merge(common)
end
defp reduce_resolution(%{middleware: []} = res), do: res
defp reduce_resolution(%{middleware: [middleware | remaining_middleware]} = res) do
case call_middleware(middleware, %{res | middleware: remaining_middleware}) do
%{state: :suspended} = res ->
res
res ->
reduce_resolution(res)
end
end
defp call_middleware({{mod, fun}, opts}, res) do
apply(mod, fun, [res, opts])
end
defp call_middleware({mod, opts}, res) do
apply(mod, :call, [res, opts])
end
defp call_middleware(mod, res) when is_atom(mod) do
apply(mod, :call, [res, []])
end
defp call_middleware(fun, res) when is_function(fun, 2) do
fun.(res, [])
end
defp build_result(%{errors: errors} = res, exec, source, path) do
%{
value: value,
definition: bp_field,
extensions: extensions,
} = res
full_type = Type.expand(bp_field.schema_node.type, exec.schema)
bp_field = put_in(bp_field.schema_node.type, full_type)
# if there are any errors, the value is always nil
value = case errors do
[] -> value
_ -> nil
end
errors = maybe_add_non_null_error(errors, value, full_type)
value
|> to_result(bp_field, full_type, extensions)
|> add_errors(Enum.reverse(errors), &put_result_error_value(&1, &2, bp_field, source, path))
|> walk_result(bp_field, full_type, exec, path)
|> propagate_null_trimming
end
defp maybe_add_non_null_error(errors, nil, %Type.NonNull{}) do
["Cannot return null for non-nullable field" | errors]
end
defp maybe_add_non_null_error(errors, _, _) do
errors
end
defp propagate_null_trimming({%{values: values} = node, exec}) do
values = Enum.map(values, &do_propagate_null_trimming/1)
node = %{node | values: values}
{do_propagate_null_trimming(node), exec}
end
defp propagate_null_trimming({node, exec}) do
{do_propagate_null_trimming(node), exec}
end
defp do_propagate_null_trimming(node) do
if bad_child = find_bad_child(node) do
bp_field = node.emitter
full_type = with %{type: type} <- bp_field.schema_node do
type
end
nil
|> to_result(bp_field, full_type, node.extensions)
|> Map.put(:errors, bad_child.errors)
# ^ We don't have to worry about clobbering the current node's errors because,
# if it had any errors, it wouldn't have any children and we wouldn't be
# here anyway.
else
node
end
end
defp find_bad_child(%{fields: fields}) do
Enum.find(fields, &non_null_violation?/1)
end
defp find_bad_child(%{values: values}) do
Enum.find(values, &non_null_list_violation?/1)
end
defp find_bad_child(_) do
false
end
# FIXME: Not super happy with this lookup process
defp non_null_violation?(%{value: nil, emitter: %{schema_node: %{type: %Type.NonNull{}}}}) do
true
end
defp non_null_violation?(_) do
false
end
# FIXME: Not super happy with this lookup process.
# Also it would be nice if we could use the same function as above.
defp non_null_list_violation?(%{value: nil, emitter: %{schema_node: %{type: %Type.List{of_type: %Type.NonNull{}}}}}) do
true
end
defp non_null_list_violation?(_) do
false
end
# defp maybe_add_non_null_error(errors, nil, %)
defp add_errors(result, errors, fun) do
Enum.reduce(errors, result, fun)
end
defp put_result_error_value(error_value, result, bp_field, source, path) do
case split_error_value(error_value) do
{[], _} ->
raise Absinthe.Resolution.result_error(error_value, bp_field, source)
{[message: message], extra} ->
put_error(result, error(bp_field, message, path, Map.new(extra)))
end
end
defp split_error_value(error_value) when is_list(error_value) or is_map(error_value) do
Keyword.split(Enum.to_list(error_value), [:message])
end
defp split_error_value(error_value) when is_binary(error_value) do
{[message: error_value], []}
end
defp split_error_value(error_value) do
{[message: to_string(error_value)], []}
end
defp to_result(nil, blueprint, _, extensions) do
%Result.Leaf{emitter: blueprint, value: nil, extensions: extensions}
end
defp to_result(root_value, blueprint, %Type.NonNull{of_type: inner_type}, extensions) do
to_result(root_value, blueprint, inner_type, extensions)
end
defp to_result(root_value, blueprint, %Type.Object{}, extensions) do
%Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions}
end
defp to_result(root_value, blueprint, %Type.Interface{}, extensions) do
%Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions}
end
defp to_result(root_value, blueprint, %Type.Union{}, extensions) do
%Result.Object{root_value: root_value, emitter: blueprint, extensions: extensions}
end
defp to_result(root_value, blueprint, %Type.List{of_type: inner_type}, extensions) do
values =
root_value
|> List.wrap
|> Enum.map(&to_result(&1, blueprint, inner_type, extensions))
%Result.List{values: values, emitter: blueprint, extensions: extensions}
end
defp to_result(root_value, blueprint, %Type.Scalar{}, extensions) do
%Result.Leaf{
emitter: blueprint,
value: root_value,
extensions: extensions,
}
end
defp to_result(root_value, blueprint, %Type.Enum{}, extensions) do
%Result.Leaf{
emitter: blueprint,
value: root_value,
extensions: extensions,
}
end
def error(node, message, path, extra) do
%Phase.Error{
phase: __MODULE__,
message: message,
locations: [node.source_location],
path: Absinthe.Resolution.path(%{path: path}),
extra: extra,
}
end
end