@@ -12,7 +12,14 @@ class Runtime
1212 module GraphQLResult
1313 # These methods are private concerns of GraphQL-Ruby,
1414 # 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
1623 end
1724
1825 class GraphQLResultHash < Hash
@@ -204,7 +211,7 @@ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_co
204211 # the field's return type at this path in order
205212 # to propagate `null`
206213 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 )
208215 end
209216 # Set this before calling `run_with_directives`, so that the directive can have the latest path
210217 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
321328 end
322329 end
323330
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+
324343 def set_result ( selection_result , result_name , value )
325- if !selection_result . graphql_dead
344+ if !dead_result? ( selection_result )
326345 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+ )
329350 # 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?
330358 parent = selection_result . graphql_parent
331359 name_in_parent = selection_result . graphql_result_name
332360 if parent . nil? # This is a top-level result hash
333361 @response = nil
334362 else
335363 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
336367 end
337368 else
338369 selection_result [ result_name ] = value
@@ -345,11 +376,10 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
345376 case value
346377 when nil
347378 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 )
350382 schema . type_error ( err , context )
351- set_result ( selection_result , result_name , nil )
352- selection_result . graphql_dead = true
353383 end
354384 else
355385 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
360390 # to avoid the overhead of checking three different classes
361391 # every time.
362392 if value . is_a? ( GraphQL ::ExecutionError )
363- if !selection_result . graphql_dead
393+ if !dead_result? ( selection_result )
364394 value . path ||= path
365395 value . ast_node ||= ast_node
366396 context . errors << value
@@ -376,7 +406,6 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
376406 err
377407 end
378408 continue_value ( path , next_value , parent_type , field , is_non_null , ast_node , result_name , selection_result )
379- HALT
380409 elsif GraphQL ::Execution ::Execute ::SKIP == value
381410 HALT
382411 else
@@ -387,7 +416,7 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
387416 when Array
388417 # It's an array full of execution errors; add them all.
389418 if value . any? && value . all? { |v | v . is_a? ( GraphQL ::ExecutionError ) }
390- if !selection_result . graphql_dead
419+ if !dead_result? ( selection_result )
391420 value . each_with_index do |error , index |
392421 error . ast_node ||= ast_node
393422 error . path ||= path + ( field . type . list? ? [ index ] : [ ] )
@@ -466,7 +495,7 @@ def continue_field(path, value, owner_type, field, current_type, ast_node, next_
466495 when "LIST"
467496 inner_type = current_type . of_type
468497 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?
470499 response_list . graphql_parent = selection_result
471500 response_list . graphql_result_name = result_name
472501 set_result ( selection_result , result_name , response_list )
@@ -585,7 +614,7 @@ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:,
585614 if eager
586615 lazy . value
587616 else
588- result [ result_name ] = lazy
617+ set_result ( result , result_name , lazy )
589618 lazy
590619 end
591620 else
0 commit comments