@@ -22,12 +22,13 @@ def initialize
22
22
end
23
23
24
24
module GraphQLResult
25
- def initialize ( result_name , parent_result )
25
+ def initialize ( result_name , parent_result , is_non_null_in_parent )
26
26
@graphql_parent = parent_result
27
27
if parent_result && parent_result . graphql_dead
28
28
@graphql_dead = true
29
29
end
30
30
@graphql_result_name = result_name
31
+ @graphql_is_non_null_in_parent = is_non_null_in_parent
31
32
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
32
33
@graphql_metadata = nil
33
34
end
@@ -42,22 +43,14 @@ def build_path(path_array)
42
43
end
43
44
44
45
attr_accessor :graphql_dead
45
- attr_reader :graphql_parent , :graphql_result_name
46
-
47
- # Although these are used by only one of the Result classes,
48
- # it's handy to have the methods implemented on both (even though they just return `nil`)
49
- # because it makes it easy to check if anything is assigned.
50
- # @return [nil, Array<String>]
51
- attr_accessor :graphql_non_null_field_names
52
- # @return [nil, true]
53
- attr_accessor :graphql_non_null_list_items
46
+ attr_reader :graphql_parent , :graphql_result_name , :graphql_is_non_null_in_parent
54
47
55
48
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
56
49
attr_accessor :graphql_result_data
57
50
end
58
51
59
52
class GraphQLResultHash
60
- def initialize ( _result_name , _parent_result )
53
+ def initialize ( _result_name , _parent_result , _is_non_null_in_parent )
61
54
super
62
55
@graphql_result_data = { }
63
56
end
@@ -145,7 +138,7 @@ def merge_into(into_result)
145
138
class GraphQLResultArray
146
139
include GraphQLResult
147
140
148
- def initialize ( _result_name , _parent_result )
141
+ def initialize ( _result_name , _parent_result , _is_non_null_in_parent )
149
142
super
150
143
@graphql_result_data = [ ]
151
144
end
@@ -209,7 +202,7 @@ def initialize(query:, lazies_at_depth:)
209
202
@schema = query . schema
210
203
@context = query . context
211
204
@multiplex_context = query . multiplex . context
212
- @response = GraphQLResultHash . new ( nil , nil )
205
+ @response = GraphQLResultHash . new ( nil , nil , false )
213
206
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
214
207
@runtime_directive_names = [ ]
215
208
noop_resolve_owner = GraphQL ::Schema ::Directive . singleton_class
@@ -276,7 +269,7 @@ def run_eager
276
269
# directly evaluated and the results can be written right into the main response hash.
277
270
tap_or_each ( gathered_selections ) do |selections , is_selection_array |
278
271
if is_selection_array
279
- selection_response = GraphQLResultHash . new ( nil , nil )
272
+ selection_response = GraphQLResultHash . new ( nil , nil , false )
280
273
final_response = @response
281
274
else
282
275
selection_response = @response
@@ -442,9 +435,6 @@ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, o
442
435
# the field's return type at this path in order
443
436
# to propagate `null`
444
437
return_type_non_null = return_type . non_null?
445
- if return_type_non_null
446
- ( selections_result . graphql_non_null_field_names ||= [ ] ) . push ( result_name )
447
- end
448
438
# Set this before calling `run_with_directives`, so that the directive can have the latest path
449
439
st = get_current_runtime_state
450
440
st . current_field = field_defn
@@ -589,13 +579,9 @@ def dead_result?(selection_result)
589
579
selection_result . graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
590
580
end
591
581
592
- def set_result ( selection_result , result_name , value , is_child_result )
582
+ def set_result ( selection_result , result_name , value , is_child_result , is_non_null )
593
583
if !dead_result? ( selection_result )
594
- if value . nil? &&
595
- ( # there are two conditions under which `nil` is not allowed in the response:
596
- ( selection_result . graphql_non_null_list_items ) || # this value would be written into a list that doesn't allow nils
597
- ( ( 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
598
- )
584
+ if value . nil? && is_non_null
599
585
# This is an invalid nil that should be propagated
600
586
# One caller of this method passes a block,
601
587
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
@@ -605,11 +591,12 @@ def set_result(selection_result, result_name, value, is_child_result)
605
591
# TODO the code is trying to tell me something.
606
592
yield if block_given?
607
593
parent = selection_result . graphql_parent
608
- name_in_parent = selection_result . graphql_result_name
609
594
if parent . nil? # This is a top-level result hash
610
595
@response = nil
611
596
else
612
- set_result ( parent , name_in_parent , nil , false )
597
+ name_in_parent = selection_result . graphql_result_name
598
+ is_non_null_in_parent = selection_result . graphql_is_non_null_in_parent
599
+ set_result ( parent , name_in_parent , nil , false , is_non_null_in_parent )
613
600
set_graphql_dead ( selection_result )
614
601
end
615
602
elsif is_child_result
@@ -651,13 +638,13 @@ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name
651
638
case value
652
639
when nil
653
640
if is_non_null
654
- set_result ( selection_result , result_name , nil , false ) do
641
+ set_result ( selection_result , result_name , nil , false , is_non_null ) do
655
642
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
656
643
err = parent_type ::InvalidNullError . new ( parent_type , field , value )
657
644
schema . type_error ( err , context )
658
645
end
659
646
else
660
- set_result ( selection_result , result_name , nil , false )
647
+ set_result ( selection_result , result_name , nil , false , is_non_null )
661
648
end
662
649
HALT
663
650
when GraphQL ::Error
@@ -670,7 +657,7 @@ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name
670
657
value . ast_node ||= ast_node
671
658
context . errors << value
672
659
if selection_result
673
- set_result ( selection_result , result_name , nil , false )
660
+ set_result ( selection_result , result_name , nil , false , is_non_null )
674
661
end
675
662
end
676
663
HALT
@@ -724,9 +711,9 @@ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name
724
711
if selection_result
725
712
if list_type_at_all
726
713
result_without_errors = value . map { |v | v . is_a? ( GraphQL ::ExecutionError ) ? nil : v }
727
- set_result ( selection_result , result_name , result_without_errors , false )
714
+ set_result ( selection_result , result_name , result_without_errors , false , is_non_null )
728
715
else
729
- set_result ( selection_result , result_name , nil , false )
716
+ set_result ( selection_result , result_name , nil , false , is_non_null )
730
717
end
731
718
end
732
719
end
@@ -736,7 +723,7 @@ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name
736
723
end
737
724
when GraphQL ::Execution ::Interpreter ::RawValue
738
725
# Write raw value directly to the response without resolving nested objects
739
- set_result ( selection_result , result_name , value . resolve , false )
726
+ set_result ( selection_result , result_name , value . resolve , false , is_non_null )
740
727
HALT
741
728
else
742
729
value
@@ -764,7 +751,7 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
764
751
rescue StandardError => err
765
752
schema . handle_or_reraise ( context , err )
766
753
end
767
- set_result ( selection_result , result_name , r , false )
754
+ set_result ( selection_result , result_name , r , false , is_non_null )
768
755
r
769
756
when "UNION" , "INTERFACE"
770
757
resolved_type_or_lazy = resolve_type ( current_type , value )
@@ -782,7 +769,7 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
782
769
err_class = current_type ::UnresolvedTypeError
783
770
type_error = err_class . new ( resolved_value , field , parent_type , resolved_type , possible_types )
784
771
schema . type_error ( type_error , context )
785
- set_result ( selection_result , result_name , nil , false )
772
+ set_result ( selection_result , result_name , nil , false , is_non_null )
786
773
nil
787
774
else
788
775
continue_field ( resolved_value , owner_type , field , resolved_type , ast_node , next_selections , is_non_null , owner_object , arguments , result_name , selection_result )
@@ -797,8 +784,8 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
797
784
after_lazy ( object_proxy , ast_node : ast_node , field : field , owner_object : owner_object , arguments : arguments , trace : false , result_name : result_name , result : selection_result ) do |inner_object |
798
785
continue_value = continue_value ( inner_object , owner_type , field , is_non_null , ast_node , result_name , selection_result )
799
786
if HALT != continue_value
800
- response_hash = GraphQLResultHash . new ( result_name , selection_result )
801
- set_result ( selection_result , result_name , response_hash , true )
787
+ response_hash = GraphQLResultHash . new ( result_name , selection_result , is_non_null )
788
+ set_result ( selection_result , result_name , response_hash , true , is_non_null )
802
789
gathered_selections = gather_selections ( continue_value , current_type , next_selections )
803
790
# There are two possibilities for `gathered_selections`:
804
791
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -810,7 +797,7 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
810
797
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
811
798
tap_or_each ( gathered_selections ) do |selections , is_selection_array |
812
799
if is_selection_array
813
- this_result = GraphQLResultHash . new ( result_name , selection_result )
800
+ this_result = GraphQLResultHash . new ( result_name , selection_result , is_non_null )
814
801
final_result = response_hash
815
802
else
816
803
this_result = response_hash
@@ -843,12 +830,12 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
843
830
# This is true for objects, unions, and interfaces
844
831
use_dataloader_job = !inner_type . unwrap . kind . input?
845
832
inner_type_non_null = inner_type . non_null?
846
- response_list = GraphQLResultArray . new ( result_name , selection_result )
847
- response_list . graphql_non_null_list_items = inner_type_non_null
848
- set_result ( selection_result , result_name , response_list , true )
849
- idx = 0
833
+ response_list = GraphQLResultArray . new ( result_name , selection_result , is_non_null )
834
+ set_result ( selection_result , result_name , response_list , true , is_non_null )
835
+ idx = nil
850
836
list_value = begin
851
837
value . each do |inner_value |
838
+ idx ||= 0
852
839
this_idx = idx
853
840
idx += 1
854
841
if use_dataloader_job
@@ -879,8 +866,9 @@ def continue_field(value, owner_type, field, current_type, ast_node, next_select
879
866
ex_err
880
867
end
881
868
end
882
-
883
- continue_value ( list_value , owner_type , field , inner_type . non_null? , ast_node , result_name , selection_result )
869
+ # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
870
+ error_is_non_null = idx . nil? ? is_non_null : inner_type . non_null?
871
+ continue_value ( list_value , owner_type , field , error_is_non_null , ast_node , result_name , selection_result )
884
872
else
885
873
raise "Invariant: Unhandled type kind #{ current_type . kind } (#{ current_type } )"
886
874
end
@@ -1007,7 +995,7 @@ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:,
1007
995
if eager
1008
996
lazy . value
1009
997
else
1010
- set_result ( result , result_name , lazy , false )
998
+ set_result ( result , result_name , lazy , false , false ) # is_non_null is irrelevant here
1011
999
current_depth = 0
1012
1000
while result
1013
1001
current_depth += 1
0 commit comments