@@ -3,26 +3,30 @@ module Execution
3
3
# A query execution strategy that emits
4
4
# `{ path: [...], value: ... }` patches as it
5
5
# resolves the query.
6
+ #
7
+ # TODO: how to handle if a selection set defers one member,
8
+ # but a later member gets InvalidNullError?
6
9
class DeferredExecution
7
10
include GraphQL ::Language
8
11
9
12
def execute ( ast_operation , root_type , query_object )
10
13
collector = query_object . context [ :collector ]
11
- exec_context = Execution ::Context . new ( query_object , self )
12
- initial_defers = [ ]
13
- initial_frame = Frame . new (
14
+
15
+ scope = ExecScope . new ( query_object )
16
+ initial_thread = ExecThread . new
17
+ initial_frame = ExecFrame . new (
14
18
node : ast_operation ,
15
19
value : query_object . root_value ,
16
- type_defn : root_type ,
17
- exec_context : exec_context ,
18
- path : [ ] ,
20
+ type : root_type ,
21
+ path : [ ]
19
22
)
20
- initial_result = resolve_or_defer_frame ( initial_frame , initial_defers )
23
+
24
+ initial_result = resolve_or_defer_frame ( scope , initial_thread , initial_frame )
21
25
22
26
if collector
23
27
initial_patch = { "data" => initial_result }
24
28
25
- initial_errors = initial_frame . errors + query_object . context . errors
29
+ initial_errors = initial_thread . errors + query_object . context . errors
26
30
error_idx = initial_errors . length
27
31
28
32
if initial_errors . any?
@@ -31,122 +35,138 @@ def execute(ast_operation, root_type, query_object)
31
35
32
36
collector . patch ( [ ] , initial_patch )
33
37
34
- defers = initial_defers + initial_frame . defers
38
+ defers = initial_thread . defers
35
39
while defers . any?
36
40
next_defers = [ ]
37
41
defers . each do |deferred_frame |
38
- deferred_result = resolve_frame ( deferred_frame )
42
+ deferred_thread = ExecThread . new
43
+ deferred_result = resolve_frame ( scope , deferred_thread , deferred_frame )
39
44
# No use patching for nil, that's there already
40
45
if !deferred_result . nil?
41
46
collector . patch ( [ "data" ] + deferred_frame . path , deferred_result )
42
47
end
43
- deferred_frame . errors . each do |deferred_error |
48
+ deferred_thread . errors . each do |deferred_error |
44
49
collector . patch ( [ "errors" , error_idx ] , deferred_error . to_h )
45
50
error_idx += 1
46
51
end
47
- next_defers . push ( *deferred_frame . defers )
52
+ next_defers . push ( *deferred_thread . defers )
48
53
end
49
54
defers = next_defers
50
55
end
51
56
else
52
- query_object . context . errors . push ( *initial_frame . errors )
57
+ query_object . context . errors . push ( *initial_thread . errors )
53
58
end
54
59
55
60
initial_result
56
61
end
57
62
58
- class Frame
59
- attr_reader :node , :value , :type_defn , :exec_context , :path , :defers , :errors
60
- def initialize ( node :, value :, type_defn :, exec_context :, path :)
61
- @node = node
62
- @value = value
63
- @type_defn = type_defn
64
- @exec_context = exec_context
65
- @path = path
66
- @defers = [ ]
63
+ # Global, window-like object for a query
64
+ class ExecScope
65
+ attr_reader :query , :schema
66
+
67
+ def initialize ( query )
68
+ @query = query
69
+ @schema = query . schema
70
+ end
71
+
72
+ def get_type ( type )
73
+ @schema . types [ type ]
74
+ end
75
+
76
+ def get_fragment ( name )
77
+ @query . fragments [ name ]
78
+ end
79
+
80
+ def get_field ( type , name )
81
+ @schema . get_field ( type , name ) || raise ( "No field named '#{ name } ' found for #{ type } " )
82
+ end
83
+ end
84
+
85
+ # One serial stream of execution
86
+ class ExecThread
87
+ attr_reader :errors , :defers
88
+ def initialize
67
89
@errors = [ ]
90
+ @defers = [ ]
68
91
end
92
+ end
69
93
70
- def spawn_child ( child_options )
71
- own_options = {
72
- node : @node ,
73
- value : @value ,
74
- type_defn : @type_defn ,
75
- exec_context : @exec_context ,
76
- path : @path ,
77
- }
78
- init_options = own_options . merge ( child_options )
79
- self . class . new ( init_options )
94
+ # One step of execution
95
+ class ExecFrame
96
+ attr_reader :node , :path , :type , :value
97
+ def initialize ( node :, path :, type :, value :)
98
+ @node = node
99
+ @path = path
100
+ @type = type
101
+ @value = value
80
102
end
81
103
end
82
104
83
105
private
84
106
85
107
# If this `frame` is marked as defer, add it to `defers`
86
108
# Otherwise, resolve it.
87
- def resolve_or_defer_frame ( frame , defers )
109
+ def resolve_or_defer_frame ( scope , thread , frame )
88
110
if frame . node . directives . any? { |dir | dir . name == "defer" }
89
- defers << frame
111
+ thread . defers << frame
90
112
nil
91
113
else
92
- resolve_frame ( frame )
114
+ resolve_frame ( scope , thread , frame )
93
115
end
94
116
end
95
117
96
118
# Determine this frame's result and write it into `#result`.
97
119
# Anything marked as `@defer` will be deferred.
98
- def resolve_frame ( frame )
120
+ def resolve_frame ( scope , thread , frame )
99
121
ast_node = frame . node
100
122
case ast_node
101
123
when Nodes ::OperationDefinition
102
- resolve_selections ( ast_node , frame )
124
+ resolve_selections ( scope , thread , frame )
103
125
when Nodes ::Field
104
- type_defn = frame . type_defn
105
- # Use Context because it provides dynamic fields too (like __typename)
106
- field_defn = frame . exec_context . get_field ( type_defn , ast_node . name )
126
+ type_defn = frame . type
127
+ # Use scope because it provides dynamic fields too (like __typename)
128
+ field_defn = scope . get_field ( type_defn , ast_node . name )
107
129
108
- field_result = resolve_field_frame ( field_defn , frame )
130
+ field_result = resolve_field_frame ( scope , thread , frame , field_defn )
109
131
return_type_defn = field_defn . type
110
132
111
- if field_result . is_a? ( GraphQL ::ExecutionError )
112
- field_result . ast_node = ast_node
113
- frame . errors << field_result
114
- nil
115
- else
116
133
resolve_value (
117
- type_defn : return_type_defn ,
118
- value : field_result ,
119
- frame : frame ,
134
+ scope ,
135
+ thread ,
136
+ frame ,
137
+ field_result ,
138
+ return_type_defn ,
120
139
)
121
- end
122
- # when Nodes::FragmentSpread
123
140
else
124
141
raise ( "No defined resolution for #{ ast_node . class . name } (#{ ast_node } )" )
125
142
end
126
143
end
127
144
128
- def resolve_selections ( ast_node , outer_frame )
145
+ def resolve_selections ( scope , thread , outer_frame )
129
146
merged_selections = GraphQL ::Execution ::SelectionOnType . flatten (
130
- outer_frame . exec_context ,
147
+ scope ,
131
148
outer_frame . value ,
132
- outer_frame . type_defn ,
133
- ast_node
149
+ outer_frame . type ,
150
+ outer_frame . node ,
134
151
)
135
152
136
153
resolved_selections = merged_selections . each_with_object ( { } ) do |ast_selection , memo |
137
154
selection_key = path_step ( ast_selection )
138
155
139
- inner_frame = outer_frame . spawn_child (
156
+ inner_frame = ExecFrame . new (
140
157
node : ast_selection ,
141
158
path : outer_frame . path + [ selection_key ] ,
159
+ type : outer_frame . type ,
160
+ value : outer_frame . value ,
142
161
)
143
162
144
- inner_result = resolve_or_defer_frame ( inner_frame , outer_frame . defers )
145
- outer_frame . errors . push ( *inner_frame . errors )
146
- outer_frame . defers . push ( *inner_frame . defers )
163
+ inner_result = resolve_or_defer_frame ( scope , thread , inner_frame )
147
164
memo [ selection_key ] = inner_result
148
165
end
149
166
resolved_selections
167
+ rescue GraphQL ::InvalidNullError => err
168
+ err . parent_error? || thread . errors << err
169
+ nil
150
170
end
151
171
152
172
def path_step ( ast_node )
@@ -158,11 +178,11 @@ def path_step(ast_node)
158
178
end
159
179
end
160
180
161
- def resolve_field_frame ( field_defn , frame )
181
+ def resolve_field_frame ( scope , thread , frame , field_defn )
162
182
ast_node = frame . node
163
- type_defn = frame . type_defn
183
+ type_defn = frame . type
164
184
value = frame . value
165
- query = frame . exec_context . query
185
+ query = scope . query
166
186
167
187
# Build arguments according to query-string literals, default values, and query variables
168
188
arguments = GraphQL ::Query ::LiteralInput . from_arguments (
@@ -172,10 +192,10 @@ def resolve_field_frame(field_defn, frame)
172
192
)
173
193
174
194
# This is the last call in the middleware chain; it actually calls the user's resolve proc
175
- field_resolve_middleware_proc = -> ( _parent_type , parent_object , field_definition , field_args , context , _next ) {
176
- context . ast_node = ast_node
177
- value = field_definition . resolve ( parent_object , field_args , context )
178
- context . ast_node = nil
195
+ field_resolve_middleware_proc = -> ( _parent_type , parent_object , field_definition , field_args , query_ctx , _next ) {
196
+ query_ctx . ast_node = ast_node
197
+ value = field_definition . resolve ( parent_object , field_args , query_ctx )
198
+ query_ctx . ast_node = nil
179
199
value
180
200
}
181
201
@@ -188,13 +208,20 @@ def resolve_field_frame(field_defn, frame)
188
208
)
189
209
190
210
begin
191
- chain . call
211
+ resolve_fn_value = chain . call
192
212
rescue GraphQL ::ExecutionError => err
193
- err
213
+ resolve_fn_value = err
194
214
end
215
+
216
+ if resolve_fn_value . is_a? ( GraphQL ::ExecutionError )
217
+ thread . errors << resolve_fn_value
218
+ resolve_fn_value . ast_node = ast_node
219
+ end
220
+
221
+ resolve_fn_value
195
222
end
196
223
197
- def resolve_value ( type_defn : , value : , frame : )
224
+ def resolve_value ( scope , thread , frame , value , type_defn )
198
225
if value . nil? || value . is_a? ( GraphQL ::ExecutionError )
199
226
if type_defn . kind . non_null?
200
227
raise GraphQL ::InvalidNullError . new ( frame . node . name , value )
@@ -207,36 +234,35 @@ def resolve_value(type_defn:, value:, frame:)
207
234
type_defn . coerce_result ( value )
208
235
when GraphQL ::TypeKinds ::NON_NULL
209
236
wrapped_type = type_defn . of_type
210
- resolve_value ( type_defn : wrapped_type , value : value , frame : frame )
237
+ resolve_value ( scope , thread , frame , value , wrapped_type )
211
238
when GraphQL ::TypeKinds ::LIST
212
239
wrapped_type = type_defn . of_type
213
240
resolved_values = value . each_with_index . map do |item , idx |
214
- inner_frame = frame . spawn_child ( {
215
- path : frame . path + [ idx ]
241
+ inner_frame = ExecFrame . new ( {
242
+ node : frame . node ,
243
+ path : frame . path + [ idx ] ,
244
+ type : wrapped_type ,
245
+ value : item ,
216
246
} )
217
- inner_result = resolve_value ( type_defn : wrapped_type , value : item , frame : inner_frame )
218
- frame . errors . push ( *inner_frame . errors )
219
- frame . defers . push ( *inner_frame . defers )
220
- inner_result
247
+ resolve_value ( scope , thread , inner_frame , item , wrapped_type )
221
248
end
222
249
resolved_values
223
250
when GraphQL ::TypeKinds ::INTERFACE , GraphQL ::TypeKinds ::UNION
224
- resolved_type = type_defn . resolve_type ( value , frame . exec_context )
251
+ resolved_type = type_defn . resolve_type ( value , scope )
225
252
226
253
if !resolved_type . is_a? ( GraphQL ::ObjectType )
227
254
raise GraphQL ::ObjectType ::UnresolvedTypeError . new ( type_defn , value )
228
255
else
229
- resolve_value ( value : value , type_defn : resolved_type , frame : frame )
256
+ resolve_value ( scope , thread , frame , value , resolved_type )
230
257
end
231
258
when GraphQL ::TypeKinds ::OBJECT
232
- inner_frame = frame . spawn_child (
259
+ inner_frame = ExecFrame . new (
260
+ node : frame . node ,
261
+ path : frame . path ,
233
262
value : value ,
234
- type_defn : type_defn ,
263
+ type : type_defn ,
235
264
)
236
- inner_result = resolve_selections ( frame . node , inner_frame )
237
- frame . errors . push ( *inner_frame . errors )
238
- frame . defers . push ( *inner_frame . defers )
239
- inner_result
265
+ resolve_selections ( scope , thread , inner_frame )
240
266
else
241
267
raise ( "No ResolveValue for kind: #{ type_defn . kind . name } (#{ type_defn } )" )
242
268
end
0 commit comments