@@ -31,6 +31,16 @@ def initialize(result_name, parent_result)
3131 # @return [nil, true]
3232 attr_accessor :graphql_non_null_list_items
3333
34+ # @return [nil, true]
35+ attr_accessor :graphql_skip_list_items_that_raise
36+
37+ def has_graphql_graph_parent_that_skips_list_items_that_raise
38+ return @has_graphql_graph_parent_that_skips_list_items_that_raise if defined? ( @has_graphql_graph_parent_that_skips_list_items_that_raise )
39+
40+ @has_graphql_graph_parent_that_skips_list_items_that_raise = graphql_skip_list_items_that_raise ||
41+ !!graphql_parent &.has_graphql_graph_parent_that_skips_list_items_that_raise
42+ end
43+
3444 # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
3545 attr_accessor :graphql_result_data
3646 end
@@ -513,13 +523,18 @@ def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, fie
513523 rescue GraphQL ::ExecutionError => err
514524 err
515525 rescue StandardError => err
516- begin
517- query . handle_or_reraise ( err )
518- rescue GraphQL ::ExecutionError => ex_err
519- ex_err
526+ if selection_result . has_graphql_graph_parent_that_skips_list_items_that_raise
527+ context . skip_from_parent_list
528+ else
529+ begin
530+ query . handle_or_reraise ( err )
531+ rescue GraphQL ::ExecutionError => ex_err
532+ ex_err
533+ end
520534 end
521535 end
522536 after_lazy ( app_result , owner : owner_type , field : field_defn , path : next_path , ast_node : ast_node , owner_object : object , arguments : resolved_arguments , result_name : result_name , result : selection_result ) do |inner_result |
537+ # puts("return type: #{return_type.to_type_signature}, skip_nodes_on_raise: #{return_type.skip_nodes_on_raise?}")
523538 continue_value = continue_value ( next_path , inner_result , owner_type , field_defn , return_type . non_null? , ast_node , result_name , selection_result )
524539 if HALT != continue_value
525540 continue_field ( next_path , continue_value , owner_type , field_defn , return_type , ast_node , next_selections , false , object , resolved_arguments , result_name , selection_result )
@@ -545,7 +560,27 @@ def dead_result?(selection_result)
545560
546561 def set_result ( selection_result , result_name , value )
547562 if !dead_result? ( selection_result )
548- if value . nil? &&
563+ if value == GraphQL ::Execution ::SKIP_FROM_PARENT_LIST
564+ if selection_result . graphql_skip_list_items_that_raise
565+ case selection_result
566+ when GraphQLResultHash then selection_result . delete ( result_name ) # TODO: unify `#delete` interface with `#graphql_skip_at`
567+ when GraphQLResultArray then selection_result . graphql_skip_at ( result_name )
568+ else raise "huh?"
569+ end
570+ else
571+ # Propograte up to find the first list this item can be skiped from.
572+ #
573+ parent = selection_result . graphql_parent
574+ name_in_parent = selection_result . graphql_result_name
575+ if parent . nil? # This is a top-level result hash
576+ @response = nil
577+ else
578+ set_result ( parent , name_in_parent , GraphQL ::Execution ::SKIP_FROM_PARENT_LIST )
579+ set_graphql_dead ( selection_result )
580+ end
581+ end
582+
583+ elsif value . nil? &&
549584 ( # there are two conditions under which `nil` is not allowed in the response:
550585 ( selection_result . graphql_non_null_list_items ) || # this value would be written into a list that doesn't allow nils
551586 ( ( 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
@@ -647,6 +682,12 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
647682 raise "Invariant: unexpected result class #{ selection_result . class } (#{ selection_result . inspect } )"
648683 end
649684 HALT
685+ elsif GraphQL ::Execution ::SKIP_FROM_PARENT_LIST == value
686+ unless selection_result . has_graphql_graph_parent_that_skips_list_items_that_raise
687+ raise "Cannot skip list items from lists not marked `skip_items_on_raise: true`"
688+ end
689+
690+ set_result ( selection_result , result_name , GraphQL ::Execution ::SKIP_FROM_PARENT_LIST )
650691 else
651692 # What could this actually _be_? Anyhow,
652693 # preserve the default behavior of doing nothing with it.
@@ -693,6 +734,8 @@ def continue_value(path, value, parent_type, field, is_non_null, ast_node, resul
693734 #
694735 # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
695736 def continue_field ( path , value , owner_type , field , current_type , ast_node , next_selections , is_non_null , owner_object , arguments , result_name , selection_result ) # rubocop:disable Metrics/ParameterLists
737+ # puts("current_type: #{current_type.to_type_signature}, skip_nodes_on_raise: #{current_type.skip_nodes_on_raise?} ")
738+
696739 if current_type . non_null?
697740 current_type = current_type . of_type
698741 is_non_null = true
@@ -776,10 +819,13 @@ def continue_field(path, value, owner_type, field, current_type, ast_node, next_
776819 end
777820 when "LIST"
778821 inner_type = current_type . of_type
822+ # puts("LIST: #{current_type}, #{current_type.skip_nodes_on_raise?.inspect}")
823+ # puts("Item type: #{inner_type}, #{inner_type.skip_nodes_on_raise?.inspect}")
779824 # This is true for objects, unions, and interfaces
780825 use_dataloader_job = !inner_type . unwrap . kind . input?
781826 response_list = GraphQLResultArray . new ( result_name , selection_result )
782827 response_list . graphql_non_null_list_items = inner_type . non_null?
828+ response_list . graphql_skip_list_items_that_raise = current_type . skip_nodes_on_raise?
783829 set_result ( selection_result , result_name , response_list )
784830 result_was_set = false
785831 idx = 0
0 commit comments