@@ -12,6 +12,22 @@ def initialize(multiplex, authorization:)
1212 @selected_operation = nil
1313 @dataloader = multiplex . context [ :dataloader ] ||= @schema . dataloader_class . new
1414 @resolves_lazies = @schema . resolves_lazies?
15+
16+ @runtime_directives = nil
17+ @schema . directives . each do |name , dir_class |
18+ if dir_class . runtime? && name != "if" && name != "skip"
19+ @runtime_directives ||= { }
20+ @runtime_directives [ dir_class . graphql_name ] = dir_class
21+ end
22+ end
23+
24+ if @runtime_directives . nil?
25+ @uses_runtime_directives = false
26+ @runtime_directives = EmptyObjects ::EMPTY_HASH
27+ else
28+ @uses_runtime_directives = true
29+ end
30+
1531 @lazy_cache = resolves_lazies ? { } . compare_by_identity : nil
1632 @authorization = authorization
1733 if @authorization
@@ -21,6 +37,8 @@ def initialize(multiplex, authorization:)
2137 end
2238 end
2339
40+ attr_reader :runtime_directives , :uses_runtime_directives
41+
2442 def resolve_type ( type , object , query )
2543 query . current_trace . begin_resolve_type ( type , object , query . context )
2644 resolved_type , _ignored_new_value = query . resolve_type ( type , object )
@@ -184,7 +202,7 @@ def execute
184202 result
185203 else
186204 data = result [ "data" ]
187- data = run_finalizers ( data , query )
205+ data = run_finalizers ( data , query , finalizers )
188206 errors = [ ]
189207 query . context . errors . each do |err |
190208 if err . respond_to? ( :to_h )
@@ -207,9 +225,10 @@ def execute
207225 Fiber [ :__graphql_current_multiplex ] = nil
208226 end
209227
210- def gather_selections ( type_defn , ast_selections , selections_step , query , prototype_result , into :)
228+ def gather_selections ( type_defn , ast_selections , selections_step , query , all_selections , prototype_result , into :)
211229 ast_selections . each do |ast_selection |
212230 next if !directives_include? ( query , ast_selection )
231+
213232 case ast_selection
214233 when GraphQL ::Language ::Nodes ::Field
215234 key = ast_selection . alias || ast_selection . name
@@ -227,13 +246,21 @@ def gather_selections(type_defn, ast_selections, selections_step, query, prototy
227246 when GraphQL ::Language ::Nodes ::InlineFragment
228247 type_condition = ast_selection . type &.name
229248 if type_condition . nil? || type_condition_applies? ( query . context , type_defn , type_condition )
230- gather_selections ( type_defn , ast_selection . selections , selections_step , query , prototype_result , into : into )
249+ if uses_runtime_directives && ast_selection . directives . any?
250+ all_selections << ( into = { __node : ast_selection } )
251+ all_selections << ( prototype_result = { } )
252+ end
253+ gather_selections ( type_defn , ast_selection . selections , selections_step , query , all_selections , prototype_result , into : into )
231254 end
232255 when GraphQL ::Language ::Nodes ::FragmentSpread
233- fragment_definition = query . document . definitions . find { | defn | defn . is_a? ( GraphQL :: Language :: Nodes :: FragmentDefinition ) && defn . name == ast_selection . name }
256+ fragment_definition = query . fragments [ ast_selection . name ]
234257 type_condition = fragment_definition . type . name
235258 if type_condition_applies? ( query . context , type_defn , type_condition )
236- gather_selections ( type_defn , fragment_definition . selections , selections_step , query , prototype_result , into : into )
259+ if uses_runtime_directives && ast_selection . directives . any?
260+ all_selections << ( into = { __node : ast_selection } )
261+ all_selections << ( prototype_result = { } )
262+ end
263+ gather_selections ( type_defn , fragment_definition . selections , selections_step , query , all_selections , prototype_result , into : into )
237264 end
238265 else
239266 raise ArgumentError , "Unsupported graphql selection node: #{ ast_selection . class } (#{ ast_selection . inspect } )"
@@ -252,8 +279,8 @@ def lazy?(object)
252279
253280 private
254281
255- def run_finalizers ( data , query )
256- paths_to_check = query . finalizers . map ( &:path )
282+ def run_finalizers ( data , query , finalizers )
283+ paths_to_check = finalizers . map ( &:path )
257284 paths_to_check . compact! # root-level auth errors currently come without a path
258285 # TODO dry with above?
259286 # This is also where a query-level "Step" would be used?
@@ -266,11 +293,19 @@ def run_finalizers(data, query)
266293 when "subscription"
267294 query . schema . subscription
268295 end
269- check_object_result ( query , data , root_type , selected_operation . selections , [ ] , [ ] , paths_to_check )
296+ paths_to_check . each_with_index do |path_to_check , idx |
297+ if path_to_check . empty?
298+ finalizer = finalizers [ idx ]
299+ # Path is already `[]`
300+ finalizer . finalize_graphql_result ( query , data , nil )
301+ finalizers [ idx ] = nil
302+ end
303+ end
304+ check_object_result ( query , data , root_type , selected_operation . selections , [ ] , [ ] , paths_to_check , finalizers )
270305 end
271306 end
272307
273- def check_object_result ( query , result_h , static_type , ast_selections , current_exec_path , current_result_path , paths_to_check )
308+ def check_object_result ( query , result_h , static_type , ast_selections , current_exec_path , current_result_path , paths_to_check , finalizers ) # rubocop:disable Metrics/ParameterLists
274309 current_path_len = current_exec_path . length
275310 ast_selections . each do |ast_selection |
276311 case ast_selection
@@ -279,7 +314,15 @@ def check_object_result(query, result_h, static_type, ast_selections, current_ex
279314 key = ast_selection . alias || ast_selection . name
280315 current_exec_path << key
281316 current_result_path << key
282- if paths_to_check . any? { |path_to_check | path_to_check [ current_path_len ] == key }
317+ this_finalizer_idx = nil
318+ should_continue = false
319+ paths_to_check . each_with_index do |path_to_check , idx |
320+ if this_finalizer_idx . nil? && current_exec_path == path_to_check
321+ this_finalizer_idx = idx
322+ end
323+ should_continue ||= path_to_check [ current_path_len ] == key
324+ end
325+ if should_continue
283326 result_value = result_h [ key ]
284327 field_defn = query . context . types . field ( static_type , ast_selection . name )
285328 result_type = field_defn . type
@@ -289,13 +332,20 @@ def check_object_result(query, result_h, static_type, ast_selections, current_ex
289332
290333 new_result_value = if result_value . is_a? ( Finalizer )
291334 result_value . path = current_result_path . dup
292- result_value . assign_graphql_result ( query , result_h , key )
335+ result_value . finalize_graphql_result ( query , result_h , key )
293336 result_h . key? ( key ) ? result_h [ key ] : :unassigned
337+ elsif this_finalizer_idx
338+ if ( finalizer = finalizers [ this_finalizer_idx ] )
339+ finalizer . path = current_result_path . dup
340+ finalizer . finalize_graphql_result ( query , result_h , key )
341+ finalizers [ this_finalizer_idx ] = nil
342+ end
343+ result_value
294344 else
295345 if result_type . list?
296- check_list_result ( query , result_value , result_type . of_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check )
346+ check_list_result ( query , result_value , result_type . of_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check , finalizers )
297347 elsif !result_type . kind . leaf?
298- check_object_result ( query , result_value , result_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check )
348+ check_object_result ( query , result_value , result_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check , finalizers )
299349 else
300350 result_value
301351 end
@@ -315,22 +365,22 @@ def check_object_result(query, result_h, static_type, ast_selections, current_ex
315365 end
316366 when Language ::Nodes ::InlineFragment
317367 static_type_at_result = @static_type_at [ result_h ]
318- if static_type_at_result && type_condition_applies? ( query . context , static_type_at_result , ast_selection . type . name )
319- result_h = check_object_result ( query , result_h , static_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check )
368+ if static_type_at_result && ( ( t = ast_selection . type ) . nil? || type_condition_applies? ( query . context , static_type_at_result , t . name ) )
369+ result_h = check_object_result ( query , result_h , static_type , ast_selection . selections , current_exec_path , current_result_path , paths_to_check , finalizers )
320370 end
321371 when Language ::Nodes ::FragmentSpread
322372 fragment_defn = query . document . definitions . find { |defn | defn . is_a? ( Language ::Nodes ::FragmentDefinition ) && defn . name == ast_selection . name }
323373 static_type_at_result = @static_type_at [ result_h ]
324374 if static_type_at_result && type_condition_applies? ( query . context , static_type_at_result , fragment_defn . type . name )
325- result_h = check_object_result ( query , result_h , static_type , fragment_defn . selections , current_exec_path , current_result_path , paths_to_check )
375+ result_h = check_object_result ( query , result_h , static_type , fragment_defn . selections , current_exec_path , current_result_path , paths_to_check , finalizers )
326376 end
327377 end
328378 end
329379
330380 result_h
331381 end
332382
333- def check_list_result ( query , result_arr , inner_type , ast_selections , current_exec_path , current_result_path , paths_to_check )
383+ def check_list_result ( query , result_arr , inner_type , ast_selections , current_exec_path , current_result_path , paths_to_check , finalizers ) # rubocop:disable Metrics/ParameterLists
334384 inner_type_non_null = false
335385 if inner_type . non_null?
336386 inner_type_non_null = true
@@ -342,12 +392,12 @@ def check_list_result(query, result_arr, inner_type, ast_selections, current_exe
342392 current_result_path << idx
343393 new_result = if result_item . is_a? ( Finalizer )
344394 result_item . path = current_result_path . dup
345- result_item . assign_graphql_result ( query , result_arr , idx )
395+ result_item . finalize_graphql_result ( query , result_arr , idx )
346396 result_arr [ idx ]
347397 elsif inner_type . list?
348- check_list_result ( query , result_item , inner_type . of_type , ast_selections , current_exec_path , current_result_path , paths_to_check )
398+ check_list_result ( query , result_item , inner_type . of_type , ast_selections , current_exec_path , current_result_path , paths_to_check , finalizers )
349399 elsif !inner_type . kind . leaf?
350- check_object_result ( query , result_item , inner_type , ast_selections , current_exec_path , current_result_path , paths_to_check )
400+ check_object_result ( query , result_item , inner_type , ast_selections , current_exec_path , current_result_path , paths_to_check , finalizers )
351401 else
352402 result_item
353403 end
0 commit comments