Packages
absinthe
1.5.0-rc.2
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?)
common =
Map.take(exec, [:adapter, :context, :acc, :root_value, :schema, :fragments, :fields_cache])
res =
%Absinthe.Resolution{
path: nil,
source: nil,
parent_type: nil,
middleware: nil,
definition: nil,
arguments: nil
}
|> Map.merge(common)
{result, res} =
exec.result
|> walk_result(operation, operation.schema_node, res, [operation])
|> propagate_null_trimming
exec = update_persisted_fields(exec, res)
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, res, path) do
{fields, res} = resolve_fields(bp_node, res, result.root_value, path)
{%{result | fields: fields}, res}
end
def walk_result(%{fields: fields} = result, bp_node, schema_type, res, path) do
{fields, res} = walk_results(fields, bp_node, schema_type, res, [0 | path], [])
{%{result | fields: fields}, res}
end
def walk_result(%Result.Leaf{} = result, _, _, res, _) do
{result, res}
end
def walk_result(%{values: values} = result, bp_node, schema_type, res, path) do
{values, res} = walk_results(values, bp_node, schema_type, res, [0 | path], [])
{%{result | values: values}, res}
end
def walk_result(%Absinthe.Resolution{} = old_res, _bp_node, _schema_type, res, _path) do
res = update_persisted_fields(old_res, res)
do_resolve_field(res, res.source, res.path)
end
# walk list results
defp walk_results([value | values], bp_node, inner_type, res, [i | sub_path] = path, acc) do
{result, res} = walk_result(value, bp_node, inner_type, res, path)
walk_results(values, bp_node, inner_type, res, [i + 1 | sub_path], [result | acc])
end
defp walk_results([], _, _, res, _, acc), do: {:lists.reverse(acc), res}
defp resolve_fields(parent, res, source, path) do
# parent is the parent field, we need to get the return type of that field
# that return type could be an interface or union, so let's make it concrete
parent
|> get_return_type
|> get_concrete_type(source, res)
|> case do
nil ->
{[], res}
parent_type ->
{fields, fields_cache} =
Absinthe.Resolution.Projector.project(
parent.selections,
parent_type,
path,
res.fields_cache,
res
)
res = %{res | fields_cache: fields_cache}
do_resolve_fields(fields, res, 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, res) do
Type.Union.resolve_type(parent_type, source, res)
end
defp get_concrete_type(%Type.Interface{} = parent_type, source, res) do
Type.Interface.resolve_type(parent_type, source, res)
end
defp get_concrete_type(parent_type, _source, _res) do
parent_type
end
defp do_resolve_fields([field | fields], res, source, parent_type, path, acc) do
field = %{field | parent_type: parent_type}
{result, res} = resolve_field(field, res, source, parent_type, [field | path])
do_resolve_fields(fields, res, source, parent_type, path, [result | acc])
end
defp do_resolve_fields([], res, _, _, _, acc), do: {:lists.reverse(acc), res}
def resolve_field(field, res, source, parent_type, path) do
res
|> build_resolution_struct(field, source, parent_type, path)
|> do_resolve_field(source, path)
end
# bp_field needs to have a concrete schema node, AKA no unions or interfaces
defp do_resolve_field(res, source, path) do
res
|> reduce_resolution
|> case do
%{state: :resolved} = res ->
build_result(res, source, path)
%{state: :suspended} = res ->
{res, res}
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(
res,
%{argument_data: args, schema_node: %{middleware: middleware}} = bp_field,
source,
parent_type,
path
) do
%{
res
| path: path,
state: :unresolved,
value: nil,
errors: [],
source: source,
parent_type: parent_type,
middleware: middleware,
definition: bp_field,
arguments: args
}
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(res, source, path) do
%{
value: value,
definition: bp_field,
extensions: extensions,
schema: schema,
errors: errors
} = res
full_type = Type.expand(bp_field.schema_node.type, 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, res, path)
|> propagate_null_trimming
end
defp maybe_add_non_null_error([], nil, %Type.NonNull{}) do
["Cannot return null for non-nullable field"]
end
defp maybe_add_non_null_error(errors, _, _) do
errors
end
defp propagate_null_trimming({%{values: values} = node, res}) do
values = Enum.map(values, &do_propagate_null_trimming/1)
node = %{node | values: values}
{do_propagate_null_trimming(node), res}
end
defp propagate_null_trimming({node, res}) do
{do_propagate_null_trimming(node), res}
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 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, path: error_path], extra} ->
put_error(
result,
error(bp_field, message, Enum.reverse(error_path) ++ path, Map.new(extra))
)
{[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, :path])
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