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

Commit cc3f714

Browse files
committed
Refactor: thread context all the way throught the Executor
This will allow us to report errors from anywhere in the flow and accumulate them in the ExecutionContext.
1 parent fb7cae3 commit cc3f714

File tree

1 file changed

+41
-30
lines changed

1 file changed

+41
-30
lines changed

lib/graphql/execution/executor.ex

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ defmodule GraphQL.Execution.Executor do
2929
def execute(schema, document, root_value \\ %{}, variable_values \\ %{}, operation_name \\ nil) do
3030
context = ExecutionContext.new(schema, document, root_value, variable_values, operation_name)
3131
case context.errors do
32-
[] -> execute_operation(context, context.operation, root_value)
32+
[] ->
33+
{_, result} = execute_operation(context, context.operation, root_value)
34+
{:ok, result}
3335
_ -> {:error, %{errors: Enum.dedup(context.errors)}}
3436
end
3537
end
@@ -42,10 +44,14 @@ defmodule GraphQL.Execution.Executor do
4244
@spec execute_operation(ExecutionContext.t, operation, map) :: result_data | {:error, String.t}
4345
defp execute_operation(context, operation, root_value) do
4446
type = operation_root_type(context.schema, operation)
45-
%{fields: fields} = collect_fields(context, type, operation.selectionSet)
47+
{context, %{fields: fields}} = collect_fields(context, type, operation.selectionSet)
4648
case operation.operation do
47-
:query -> {:ok, execute_fields(context, type, root_value, fields)}
48-
:mutation -> {:ok, execute_fields_serially(context, type, root_value, fields)}
49+
:query ->
50+
{_, result} = execute_fields(context, type, root_value, fields)
51+
{:ok, result}
52+
:mutation ->
53+
{_, result} = execute_fields_serially(context, type, root_value, fields)
54+
{:ok, result}
4955
:subscription -> {:error, "Subscriptions not currently supported"}
5056
_ -> {:error, "Can only execute queries, mutations and subscriptions"}
5157
end
@@ -57,12 +63,12 @@ defmodule GraphQL.Execution.Executor do
5763
end
5864

5965
defp collect_fields(context, runtime_type, selection_set, field_fragment_map \\ %{fields: %{}, fragments: %{}}) do
60-
Enum.reduce selection_set[:selections], field_fragment_map, fn(selection, field_fragment_map) ->
66+
Enum.reduce selection_set[:selections], {context, field_fragment_map}, fn(selection, {context, field_fragment_map}) ->
6167
case selection do
6268
%{kind: :Field} ->
6369
field_name = field_entry_key(selection)
6470
fields = field_fragment_map.fields[field_name] || []
65-
put_in(field_fragment_map.fields[field_name], [selection | fields])
71+
{context, put_in(field_fragment_map.fields[field_name], [selection | fields])}
6672
%{kind: :InlineFragment} ->
6773
collect_fragment(context, runtime_type, selection, field_fragment_map)
6874
%{kind: :FragmentSpread} ->
@@ -71,36 +77,37 @@ defmodule GraphQL.Execution.Executor do
7177
field_fragment_map = put_in(field_fragment_map.fragments[fragment_name], true)
7278
collect_fragment(context, runtime_type, context.fragments[fragment_name], field_fragment_map)
7379
else
74-
field_fragment_map
80+
{context, field_fragment_map}
7581
end
76-
_ -> field_fragment_map
82+
_ -> {context, field_fragment_map}
7783
end
7884
end
7985
end
8086

81-
@spec execute_fields(ExecutionContext.t, atom | Map, any, any) :: any
87+
@spec execute_fields(ExecutionContext.t, atom | Map, any, any) :: {ExecutionContext.t, map}
8288
defp execute_fields(context, parent_type, source_value, fields) when is_atom(parent_type) do
8389
execute_fields(context, apply(parent_type, :type, []), source_value, fields)
8490
end
8591

86-
@spec execute_fields(ExecutionContext.t, atom | Map, any, any) :: any
92+
@spec execute_fields(ExecutionContext.t, atom | Map, any, any) :: {ExecutionContext.t, map}
8793
defp execute_fields(context, parent_type, source_value, fields) do
88-
Enum.reduce fields, %{}, fn({field_name_ast, field_asts}, results) ->
94+
Enum.reduce fields, {context, %{}}, fn({field_name_ast, field_asts}, {context, results}) ->
8995
case resolve_field(context, parent_type, source_value, field_asts) do
90-
:undefined -> results
91-
value -> Map.put(results, field_name_ast.value, value)
96+
{context, :undefined} -> {context, results}
97+
{context, value} -> {context, Map.put(results, field_name_ast.value, value)}
9298
end
9399
end
94100
end
95101

96-
@spec execute_fields_serially(ExecutionContext.t, atom, map, any) :: any
102+
@spec execute_fields_serially(ExecutionContext.t, atom, map, any) :: {ExecutionContext.t, map}
97103
defp execute_fields_serially(context, parent_type, source_value, fields) do
98104
# call execute_fields because no async operations yet
99105
execute_fields(context, parent_type, source_value, fields)
100106
end
101107

102108
defp resolve_field(context, parent_type, source, field_asts) do
103109
field_ast = hd(field_asts)
110+
# FIXME: possible memory leak
104111
field_name = String.to_atom(field_ast.name.value)
105112

106113
if field_def = field_definition(parent_type, field_name) do
@@ -137,44 +144,46 @@ defmodule GraphQL.Execution.Executor do
137144
end
138145
complete_value_catching_error(context, return_type, field_asts, info, result)
139146
else
140-
:undefined
147+
{context, :undefined}
141148
end
142149
end
143150

151+
@spec complete_value_catching_error(ExecutionContext.t, any, GraphQL.Document.t, any, map) :: {ExecutionContext.t, map | nil}
144152
defp complete_value_catching_error(context, return_type, field_asts, info, result) do
145153
# TODO lots of error checking
146154
complete_value(context, return_type, field_asts, info, result)
147155
end
148156

149-
defp complete_value(_, _, _, _, nil), do: nil
157+
@spec complete_value(ExecutionContext.t, any, any, any, nil) :: {ExecutionContext.t, nil}
158+
defp complete_value(context, _, _, _, nil), do: {context, nil}
150159

151-
@spec complete_value(ExecutionContext.t, %ObjectType{}, GraphQL.Document.t, any, map) :: map
160+
@spec complete_value(ExecutionContext.t, %ObjectType{}, GraphQL.Document.t, any, map) :: {ExecutionContext.t, map}
152161
defp complete_value(context, %ObjectType{} = return_type, field_asts, _info, result) do
153-
sub_field_asts = collect_sub_fields(context, return_type, field_asts)
162+
{context, sub_field_asts} = collect_sub_fields(context, return_type, field_asts)
154163
execute_fields(context, return_type, result, sub_field_asts.fields)
155164
end
156165

157166
defp complete_value(context, %NonNull{ofType: inner_type}, field_asts, info, result) when is_atom(inner_type) do
158167
complete_value(context, %NonNull{ofType: apply(inner_type, :type, [])}, field_asts, info, result)
159168
end
160169

161-
@spec complete_value(ExecutionContext.t, %NonNull{}, GraphQL.Document.t, any, any) :: map
170+
@spec complete_value(ExecutionContext.t, %NonNull{}, GraphQL.Document.t, any, any) :: {ExecutionContext.t, map}
162171
defp complete_value(context, %NonNull{ofType: inner_type}, field_asts, info, result) do
163172
# TODO: Null Checking
164173
complete_value(context, inner_type, field_asts, info, result)
165174
end
166175

167-
@spec complete_value(ExecutionContext.t, %Interface{}, GraphQL.Document.t, any, any) :: map
176+
@spec complete_value(ExecutionContext.t, %Interface{}, GraphQL.Document.t, any, any) :: {ExecutionContext.t, map}
168177
defp complete_value(context, %Interface{} = return_type, field_asts, info, result) do
169178
runtime_type = AbstractType.get_object_type(return_type, result, info.schema)
170-
sub_field_asts = collect_sub_fields(context, runtime_type, field_asts)
179+
{context, sub_field_asts} = collect_sub_fields(context, runtime_type, field_asts)
171180
execute_fields(context, runtime_type, result, sub_field_asts.fields)
172181
end
173182

174-
@spec complete_value(ExecutionContext.t, %Union{}, GraphQL.Document.t, any, any) :: map
183+
@spec complete_value(ExecutionContext.t, %Union{}, GraphQL.Document.t, any, any) :: {ExecutionContext.t, map}
175184
defp complete_value(context, %Union{} = return_type, field_asts, info, result) do
176185
runtime_type = AbstractType.get_object_type(return_type, result, info.schema)
177-
sub_field_asts = collect_sub_fields(context, runtime_type, field_asts)
186+
{context, sub_field_asts} = collect_sub_fields(context, runtime_type, field_asts)
178187
execute_fields(context, runtime_type, result, sub_field_asts.fields)
179188
end
180189

@@ -184,26 +193,28 @@ defmodule GraphQL.Execution.Executor do
184193

185194
@spec complete_value(ExecutionContext.t, %List{}, GraphQL.Document.t, any, any) :: map
186195
defp complete_value(context, %List{ofType: list_type}, field_asts, info, result) do
187-
Enum.map result, fn(item) ->
188-
complete_value_catching_error(context, list_type, field_asts, info, item)
196+
{context, result} = Enum.reduce result, {context, []}, fn(item, {context, acc}) ->
197+
{context, value} = complete_value_catching_error(context, list_type, field_asts, info, item)
198+
{context, [value] ++ acc}
189199
end
200+
{context, Enum.reverse(result)}
190201
end
191202

192203
defp complete_value(context, return_type, field_asts, info, result) when is_atom(return_type) do
193204
type = apply(return_type, :type, [])
194205
complete_value(context, type, field_asts, info, result)
195206
end
196207

197-
defp complete_value(_context, return_type, _field_asts, _info, result) do
198-
GraphQL.Types.serialize(return_type, result)
208+
defp complete_value(context, return_type, _field_asts, _info, result) do
209+
{context, GraphQL.Types.serialize(return_type, result)}
199210
end
200211

201212
defp collect_sub_fields(context, return_type, field_asts) do
202-
Enum.reduce field_asts, %{fields: %{}, fragments: %{}}, fn(field_ast, field_fragment_map) ->
213+
Enum.reduce field_asts, {context, %{fields: %{}, fragments: %{}}}, fn(field_ast, {context, field_fragment_map}) ->
203214
if selection_set = Map.get(field_ast, :selectionSet) do
204215
collect_fields(context, return_type, selection_set, field_fragment_map)
205216
else
206-
field_fragment_map
217+
{context, field_fragment_map}
207218
end
208219
end
209220
end
@@ -293,7 +304,7 @@ defmodule GraphQL.Execution.Executor do
293304
if condition_matches do
294305
collect_fields(context, runtime_type, selection.selectionSet, field_fragment_map)
295306
else
296-
field_fragment_map
307+
{context, field_fragment_map}
297308
end
298309
end
299310

0 commit comments

Comments
 (0)