@@ -12,7 +12,14 @@ class Runtime
12
12
module GraphQLResult
13
13
# These methods are private concerns of GraphQL-Ruby,
14
14
# they aren't guaranteed to continue working in the future.
15
- attr_accessor :graphql_dead , :graphql_non_null , :graphql_parent , :graphql_result_name
15
+ attr_accessor :graphql_dead , :graphql_parent , :graphql_result_name
16
+ # Although these are used by only one of the Result classes,
17
+ # it's handy to have the methods implemented on both (even though they just return `nil`)
18
+ # because it makes it easy to check if anything is assigned.
19
+ # @return [nil, Array<String>]
20
+ attr_accessor :graphql_non_null_field_names
21
+ # @return [nil, true]
22
+ attr_accessor :graphql_non_null_list_items
16
23
end
17
24
18
25
class GraphQLResultHash < Hash
@@ -204,7 +211,7 @@ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_co
204
211
# the field's return type at this path in order
205
212
# to propagate `null`
206
213
if return_type . non_null?
207
- ( selections_result . graphql_non_null ||= [ ] ) . push ( result_name )
214
+ ( selections_result . graphql_non_null_field_names ||= [ ] ) . push ( result_name )
208
215
end
209
216
# Set this before calling `run_with_directives`, so that the directive can have the latest path
210
217
set_all_interpreter_context ( nil , field_defn , nil , next_path )
@@ -321,18 +328,42 @@ def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_nod
321
328
end
322
329
end
323
330
331
+ def dead_result? ( selection_result )
332
+ r = selection_result
333
+ while r
334
+ if r . graphql_dead
335
+ return true
336
+ else
337
+ r = r . graphql_parent
338
+ end
339
+ end
340
+ false
341
+ end
342
+
324
343
def set_result ( selection_result , result_name , value )
325
- if !selection_result . graphql_dead
344
+ if !dead_result? ( selection_result )
326
345
if value . nil? &&
327
- ( nn = selection_result . graphql_non_null ) &&
328
- ( nn == true || nn . include? ( result_name ) )
346
+ ( # there are two conditions under which `nil` is not allowed in the response:
347
+ ( selection_result . graphql_non_null_list_items ) || # this value would be written into a list that doesn't allow nils
348
+ ( ( nn = selection_result . graphql_non_null_field_names ) && nn . include? ( result_name ) ) # this value would be written into a field that doesn't allow nils
349
+ )
329
350
# This is an invalid nil that should be propagated
351
+ # One caller of this method passes a block,
352
+ # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
353
+ # The other possibility for reaching here is when a field returns an ExecutionError, so we write
354
+ # `nil` to the response, not knowing whether it's an invalid `nil` or not.
355
+ # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
356
+ # TODO the code is trying to tell me something.
357
+ yield if block_given?
330
358
parent = selection_result . graphql_parent
331
359
name_in_parent = selection_result . graphql_result_name
332
360
if parent . nil? # This is a top-level result hash
333
361
@response = nil
334
362
else
335
363
set_result ( parent , name_in_parent , nil )
364
+ # This is odd, but it's how it used to work. Even if `parent` _would_ accept
365
+ # a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
366
+ parent . graphql_dead = true
336
367
end
337
368
else
338
369
selection_result [ result_name ] = value
@@ -345,11 +376,10 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
345
376
case value
346
377
when nil
347
378
if is_non_null
348
- err = parent_type ::InvalidNullError . new ( parent_type , field , value )
349
- if !selection_result . graphql_dead
379
+ set_result ( selection_result , result_name , nil ) do
380
+ # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
381
+ err = parent_type ::InvalidNullError . new ( parent_type , field , value )
350
382
schema . type_error ( err , context )
351
- set_result ( selection_result , result_name , nil )
352
- selection_result . graphql_dead = true
353
383
end
354
384
else
355
385
set_result ( selection_result , result_name , nil )
@@ -360,7 +390,7 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
360
390
# to avoid the overhead of checking three different classes
361
391
# every time.
362
392
if value . is_a? ( GraphQL ::ExecutionError )
363
- if !selection_result . graphql_dead
393
+ if !dead_result? ( selection_result )
364
394
value . path ||= path
365
395
value . ast_node ||= ast_node
366
396
context . errors << value
@@ -376,7 +406,6 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
376
406
err
377
407
end
378
408
continue_value ( path , next_value , parent_type , field , is_non_null , ast_node , result_name , selection_result )
379
- HALT
380
409
elsif GraphQL ::Execution ::Execute ::SKIP == value
381
410
HALT
382
411
else
@@ -387,7 +416,7 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
387
416
when Array
388
417
# It's an array full of execution errors; add them all.
389
418
if value . any? && value . all? { |v | v . is_a? ( GraphQL ::ExecutionError ) }
390
- if !selection_result . graphql_dead
419
+ if !dead_result? ( selection_result )
391
420
value . each_with_index do |error , index |
392
421
error . ast_node ||= ast_node
393
422
error . path ||= path + ( field . type . list? ? [ index ] : [ ] )
@@ -466,7 +495,7 @@ def continue_field(path, value, owner_type, field, current_type, ast_node, next_
466
495
when "LIST"
467
496
inner_type = current_type . of_type
468
497
response_list = GraphQLResultArray . new
469
- response_list . graphql_non_null = inner_type . non_null?
498
+ response_list . graphql_non_null_list_items = inner_type . non_null?
470
499
response_list . graphql_parent = selection_result
471
500
response_list . graphql_result_name = result_name
472
501
set_result ( selection_result , result_name , response_list )
@@ -585,7 +614,7 @@ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:,
585
614
if eager
586
615
lazy . value
587
616
else
588
- result [ result_name ] = lazy
617
+ set_result ( result , result_name , lazy )
589
618
lazy
590
619
end
591
620
else
0 commit comments