graphql 2.5.22 → 2.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/execution/interpreter/runtime.rb +3 -2
  3. data/lib/graphql/execution/interpreter.rb +6 -9
  4. data/lib/graphql/execution/lazy.rb +1 -1
  5. data/lib/graphql/execution/next/field_resolve_step.rb +93 -61
  6. data/lib/graphql/execution/next/load_argument_step.rb +5 -1
  7. data/lib/graphql/execution/next/prepare_object_step.rb +2 -2
  8. data/lib/graphql/execution/next/runner.rb +48 -26
  9. data/lib/graphql/execution/next.rb +3 -1
  10. data/lib/graphql/execution.rb +7 -4
  11. data/lib/graphql/execution_error.rb +5 -1
  12. data/lib/graphql/query/context.rb +1 -1
  13. data/lib/graphql/schema/field.rb +3 -4
  14. data/lib/graphql/schema/list.rb +1 -1
  15. data/lib/graphql/schema/member/has_fields.rb +5 -1
  16. data/lib/graphql/schema/non_null.rb +1 -1
  17. data/lib/graphql/schema/resolver.rb +18 -3
  18. data/lib/graphql/schema/subscription.rb +0 -2
  19. data/lib/graphql/schema/visibility/profile.rb +68 -49
  20. data/lib/graphql/schema/wrapper.rb +7 -1
  21. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  22. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  23. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  24. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  25. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  26. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  27. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
  28. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  29. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  30. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  31. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  32. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  33. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  34. data/lib/graphql/static_validation/validation_context.rb +1 -1
  35. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +25 -1
  36. data/lib/graphql/subscriptions/event.rb +1 -0
  37. data/lib/graphql/subscriptions.rb +20 -0
  38. data/lib/graphql/tracing/perfetto_trace.rb +2 -2
  39. data/lib/graphql/unauthorized_error.rb +4 -0
  40. data/lib/graphql/version.rb +1 -1
  41. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 453860aab49f87d8b8771b00cc50b34a4567e7c80d7ce15b95c1b4fa82b23286
4
- data.tar.gz: f33d534cbbccfcfb89ba8e6f553ea17d71199ca3fc898ab75c5f3ace08aba820
3
+ metadata.gz: 724e526bb33dee5a8b39cb04b4d42f2e969e45aafe73581c5bdd02177cfa1347
4
+ data.tar.gz: f211ca87d49c5077b0ae4744ce5956795835a6405a07e8782970cde14d172d7e
5
5
  SHA512:
6
- metadata.gz: 3c2c8b87e60b9312ce5fb3848cb9a8112b6d050b6ee9c31eee5720932a2cf754f97f6f7bef273b491199aaa46c7551348e07294270585e79a1e35d176126bdfb
7
- data.tar.gz: b679a8434d195d513f97963b2fe70b296cb2106ceda4ae2b7c756d1127d8ee4c56f2e3106b4a01c609498904622ada5d569f489bf97a13f8627e7b6a6a1d83fd
6
+ metadata.gz: 5d9c249f5d7bbe3ef06070cf52b04586eedf2df06f7c1fc95c6ab47bdc8182218fff2281d76a3f2a18bef4f1ad9c508901e6c7a3f882d28390afdd2f4dca6fd0
7
+ data.tar.gz: 5179a34bc5c34769021fab1dff6384c22f73f776dc994a82f9bce635ddf6ead05ec0a3d2ff37bfcedbc892d87d2d7a82b08eab4e4cf319ee390e2c5a34db76fa
@@ -603,7 +603,7 @@ module GraphQL
603
603
  err
604
604
  end
605
605
  continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
606
- elsif GraphQL::Execution::SKIP == value
606
+ elsif value.is_a?(GraphQL::Execution::Skip)
607
607
  # It's possible a lazy was already written here
608
608
  case selection_result
609
609
  when GraphQLResultHash
@@ -761,7 +761,7 @@ module GraphQL
761
761
  idx += 1
762
762
  if use_dataloader_job
763
763
  @dataloader.append_job do
764
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
764
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, nil)
765
765
  end
766
766
  else
767
767
  resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
@@ -803,6 +803,7 @@ module GraphQL
803
803
  end
804
804
 
805
805
  def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
806
+ runtime_state ||= get_current_runtime_state
806
807
  runtime_state.current_result_name = this_idx
807
808
  runtime_state.current_result = response_list
808
809
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
@@ -58,11 +58,6 @@ module GraphQL
58
58
  # Do as much eager evaluation of the query as possible
59
59
  results = []
60
60
  queries.each_with_index do |query, idx|
61
- if query.subscription? && !query.subscription_update?
62
- subs_namespace = query.context.namespace(:subscriptions)
63
- subs_namespace[:events] = []
64
- subs_namespace[:subscriptions] = {}
65
- end
66
61
  multiplex.dataloader.append_job {
67
62
  operation = query.selected_operation
68
63
  result = if operation.nil? || !query.valid? || !query.context.errors.empty?
@@ -74,7 +69,9 @@ module GraphQL
74
69
  # in particular, assign it here:
75
70
  runtime = Runtime.new(query: query)
76
71
  query.context.namespace(:interpreter_runtime)[:runtime] = runtime
77
-
72
+ if query.subscription? && !query.subscription_update?
73
+ schema.subscriptions.initialize_subscriptions(query)
74
+ end
78
75
  query.current_trace.execute_query(query: query) do
79
76
  runtime.run_eager
80
77
  end
@@ -91,9 +88,6 @@ module GraphQL
91
88
  # Then, find all errors and assign the result to the query object
92
89
  results.each_with_index do |data_result, idx|
93
90
  query = queries[idx]
94
- if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
95
- schema.subscriptions.write_subscription(query, events)
96
- end
97
91
  # Assign the result so that it can be accessed in instrumentation
98
92
  query.result_values = if data_result.equal?(NO_OPERATION)
99
93
  if !query.valid? || !query.context.errors.empty?
@@ -103,6 +97,9 @@ module GraphQL
103
97
  data_result
104
98
  end
105
99
  else
100
+ if query.subscription?
101
+ schema.subscriptions.finish_subscriptions(query)
102
+ end
106
103
  result = {}
107
104
 
108
105
  if !query.context.errors.empty?
@@ -38,7 +38,7 @@ module GraphQL
38
38
  # (fewer clauses in a hot `case` block), but now it requires special handling here.
39
39
  # I think it's still worth it for the performance win, but if the number of special
40
40
  # cases grows, then maybe it's worth rethinking somehow.
41
- if @value.is_a?(StandardError) && @value != GraphQL::Execution::SKIP
41
+ if @value.is_a?(StandardError) && !@value.is_a?(GraphQL::Execution::Skip)
42
42
  raise @value
43
43
  else
44
44
  @value
@@ -48,59 +48,86 @@ module GraphQL
48
48
  nil
49
49
  end
50
50
 
51
- def coerce_arguments(argument_owner, ast_arguments_or_hash)
52
- arg_defns = argument_owner.arguments(@selections_step.query.context)
51
+ def coerce_arguments(argument_owner, ast_arguments_or_hash, run_loads = true)
52
+ arg_defns = @selections_step.query.types.arguments(argument_owner)
53
53
  if arg_defns.empty?
54
54
  return EmptyObjects::EMPTY_HASH
55
55
  end
56
56
  args_hash = {}
57
- if ast_arguments_or_hash.is_a?(Hash)
58
- ast_arguments_or_hash.each do |key, value|
59
- key_s = nil
60
- arg_defn = arg_defns.each_value.find { |a|
61
- a.keyword == key || a.graphql_name == (key_s ||= String(key))
62
- }
63
- coerce_argument_value(args_hash, arg_defn, value)
64
- end
65
- else
66
- ast_arguments_or_hash.each { |arg_node|
67
- arg_defn = arg_defns[arg_node.name]
68
- coerce_argument_value(args_hash, arg_defn, arg_node.value)
69
- }
57
+
58
+ if ast_arguments_or_hash.nil? # This can happen with `.trigger`
59
+ return args_hash
70
60
  end
71
- # TODO refactor the loop above into this one
72
- arg_defns.each do |arg_graphql_name, arg_defn|
73
- if arg_defn.default_value? && !args_hash.key?(arg_defn.keyword)
74
- coerce_argument_value(args_hash, arg_defn, arg_defn.default_value)
61
+
62
+ arg_inputs_are_h = ast_arguments_or_hash.is_a?(Hash)
63
+
64
+ arg_defns.each do |arg_defn|
65
+ arg_value = nil
66
+ was_found = false
67
+ if arg_inputs_are_h
68
+ ast_arguments_or_hash.each do |key, value|
69
+ if key == arg_defn.keyword || key.to_s == arg_defn.graphql_name
70
+ arg_value = value
71
+ was_found = true
72
+ break
73
+ end
74
+ end
75
+ else
76
+ ast_arguments_or_hash.each do |arg_node|
77
+ if arg_node.name == arg_defn.graphql_name
78
+ arg_value = arg_node.value
79
+ was_found = true
80
+ break
81
+ end
82
+ end
83
+ end
84
+
85
+ if arg_value.is_a?(Language::Nodes::VariableIdentifier)
86
+ vars = @selections_step.query.variables
87
+ arg_value = if vars.key?(arg_value.name)
88
+ vars[arg_value.name]
89
+ elsif vars.key?(arg_value.name.to_sym)
90
+ vars[arg_value.name.to_sym]
91
+ else
92
+ was_found = false
93
+ nil
94
+ end
95
+ end
96
+
97
+ if !was_found && arg_defn.default_value?
98
+ was_found = true
99
+ arg_value = arg_defn.default_value
100
+ end
101
+
102
+ if was_found
103
+ coerce_argument_value(args_hash, arg_defn, arg_value, run_loads)
75
104
  end
76
105
  end
77
106
 
78
107
  args_hash
79
108
  end
80
109
 
81
- def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_defn.keyword, as_type: nil)
110
+ def coerce_argument_value(arguments, arg_defn, arg_value, run_loads, target_keyword: run_loads ? arg_defn.keyword : arg_defn.graphql_name, as_type: nil)
82
111
  arg_t = as_type || arg_defn.type
83
112
  if arg_t.non_null?
84
113
  arg_t = arg_t.of_type
85
114
  end
86
115
 
87
- arg_value = if arg_value.is_a?(Language::Nodes::VariableIdentifier)
116
+ if arg_value.is_a?(Language::Nodes::VariableIdentifier)
88
117
  vars = @selections_step.query.variables
89
- if vars.key?(arg_value.name)
118
+ arg_value = if vars.key?(arg_value.name)
90
119
  vars[arg_value.name]
91
120
  elsif vars.key?(arg_value.name.to_sym)
92
121
  vars[arg_value.name.to_sym]
93
122
  else
94
- return # not present
123
+ nil
95
124
  end
96
- elsif arg_value.is_a?(Language::Nodes::NullValue)
97
- nil
125
+ end
126
+
127
+ if arg_value.is_a?(Language::Nodes::NullValue)
128
+ arg_value = nil
98
129
  elsif arg_value.is_a?(Language::Nodes::Enum)
99
- arg_value.name
100
- elsif arg_value.is_a?(Language::Nodes::InputObject)
101
- arg_value.arguments # rubocop:disable Development/ContextIsPassedCop
102
- else
103
- arg_value
130
+ arg_value = arg_value.name
104
131
  end
105
132
 
106
133
  ctx = @selections_step.query.context
@@ -111,7 +138,7 @@ module GraphQL
111
138
  arg_value = Array(arg_value)
112
139
  inner_t = arg_t.of_type
113
140
  result = Array.new(arg_value.size)
114
- arg_value.each_with_index { |v, i| coerce_argument_value(result, arg_defn, v, target_keyword: i, as_type: inner_t) }
141
+ arg_value.each_with_index { |v, i| coerce_argument_value(result, arg_defn, v, run_loads, target_keyword: i, as_type: inner_t) }
115
142
  result
116
143
  end
117
144
  elsif arg_t.kind.leaf?
@@ -125,7 +152,8 @@ module GraphQL
125
152
  end
126
153
  end
127
154
  elsif arg_t.kind.input_object?
128
- input_obj_args = coerce_arguments(arg_t, arg_value)
155
+ input_obj_vals = arg_value.is_a?(Language::Nodes::InputObject) ? arg_value.arguments : arg_value # rubocop:disable Development/ContextIsPassedCop
156
+ input_obj_args = coerce_arguments(arg_t, input_obj_vals)
129
157
  arg_t.new(nil, ruby_kwargs: input_obj_args, context: @selections_step.query.context, defaults_used: nil)
130
158
  else
131
159
  raise "Unsupported argument value: #{arg_t.to_type_signature} / #{arg_value.class} (#{arg_value.inspect})"
@@ -145,7 +173,7 @@ module GraphQL
145
173
 
146
174
  if arg_value.is_a?(GraphQL::Error)
147
175
  @arguments = arg_value
148
- elsif arg_defn.loads && as_type.nil? && !arg_value.nil?
176
+ elsif run_loads && arg_defn.loads && as_type.nil? && !arg_value.nil?
149
177
  # This is for legacy compat:
150
178
  load_receiver = if (r = @field_definition.resolver)
151
179
  r.new(field: @field_definition, context: @selections_step.query.context, object: nil)
@@ -252,18 +280,11 @@ module GraphQL
252
280
  query = @selections_step.query
253
281
  field_name = @ast_node.name
254
282
  @field_definition = query.get_field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}")
255
- if field_name == "__typename"
256
- # TODO handle custom introspection
257
- @field_results = Array.new(@selections_step.objects.size, @parent_type.graphql_name)
258
- @object_is_authorized = AlwaysAuthorized
259
- build_results
260
- return
261
- end
262
-
263
283
  arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop
264
284
  @arguments ||= arguments # may have already been set to an error
265
285
 
266
- if @pending_steps.nil? || @pending_steps.size == 0
286
+ if (@pending_steps.nil? || @pending_steps.size == 0) &&
287
+ @field_results.nil? # Make sure the arguments flow didn't already call through
267
288
  execute_field
268
289
  end
269
290
  end
@@ -323,6 +344,14 @@ module GraphQL
323
344
  is_authed = @field_definition.authorized?(o, @arguments, ctx)
324
345
  if is_authed
325
346
  authorized_objects << o
347
+ else
348
+ begin
349
+ err = GraphQL::UnauthorizedFieldError.new(object: o, type: @parent_type, context: ctx, field: @field_definition)
350
+ authorized_objects << query.schema.unauthorized_object(err)
351
+ is_authed = true
352
+ rescue GraphQL::ExecutionError => exec_err
353
+ add_graphql_error(exec_err)
354
+ end
326
355
  end
327
356
  is_authed
328
357
  }
@@ -616,29 +645,35 @@ module GraphQL
616
645
  method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type
617
646
  case @field_definition.execution_next_mode
618
647
  when :resolve_batch
619
- if args_hash.empty?
620
- method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context)
621
- else
648
+ begin
622
649
  method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context, **args_hash)
650
+ rescue GraphQL::ExecutionError => exec_err
651
+ Array.new(objects.size, exec_err)
623
652
  end
624
653
  when :resolve_static
625
- result = if args_hash.empty?
626
- method_receiver.public_send(@field_definition.execution_next_mode_key, context)
627
- else
628
- method_receiver.public_send(@field_definition.execution_next_mode_key, context, **args_hash)
629
- end
654
+ result = method_receiver.public_send(@field_definition.execution_next_mode_key, context, **args_hash)
630
655
  Array.new(objects.size, result)
631
656
  when :resolve_each
632
- if args_hash.empty?
633
- objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context) }
634
- else
635
- objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context, **args_hash) }
657
+ objects.map do |o|
658
+ method_receiver.public_send(@field_definition.execution_next_mode_key, o, context, **args_hash)
659
+ rescue GraphQL::ExecutionError => err
660
+ err
636
661
  end
637
662
  when :hash_key
638
663
  objects.map { |o| o[@field_definition.execution_next_mode_key] }
639
664
  when :direct_send
640
665
  if args_hash.empty?
641
- objects.map { |o| o.public_send(@field_definition.execution_next_mode_key) }
666
+ objects.map do |o|
667
+ o.public_send(@field_definition.execution_next_mode_key)
668
+ rescue GraphQL::ExecutionError => err
669
+ err
670
+ rescue StandardError => stderr
671
+ begin
672
+ @selections_step.query.handle_or_reraise(stderr)
673
+ rescue GraphQL::ExecutionError => ex_err
674
+ ex_err
675
+ end
676
+ end
642
677
  else
643
678
  objects.map { |o| o.public_send(@field_definition.execution_next_mode_key, **args_hash) }
644
679
  end
@@ -684,17 +719,14 @@ module GraphQL
684
719
  if @field_definition.dynamic_introspection
685
720
  obj_inst = @owner.wrap(obj_inst, context)
686
721
  end
687
- if args_hash.empty?
688
- obj_inst.public_send(@field_definition.execution_next_mode_key)
689
- else
690
- obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
691
- end
722
+ obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
723
+ rescue GraphQL::ExecutionError => exec_err
724
+ exec_err
692
725
  end
693
726
  else
694
727
  raise "Batching execution for #{path} not implemented (execution_next_mode: #{@execution_next_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
695
728
  end
696
729
  end
697
-
698
730
  end
699
731
 
700
732
  class RawValueFieldResolveStep < FieldResolveStep
@@ -20,7 +20,11 @@ module GraphQL
20
20
 
21
21
  def call
22
22
  context = @field_resolve_step.selections_step.query.context
23
- @loaded_value = @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
23
+ @loaded_value = begin
24
+ @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
25
+ rescue GraphQL::UnauthorizedError => auth_err
26
+ context.schema.unauthorized_object(auth_err)
27
+ end
24
28
  if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
25
29
  runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
26
30
  else
@@ -72,7 +72,7 @@ module GraphQL
72
72
  begin
73
73
  query.current_trace.begin_authorized(@resolved_type, @object, query.context)
74
74
  @authorized_value = @resolved_type.authorized?(@object, query.context)
75
- query.current_trace.end_authorized(@resolve_type, @object, query.context, @authorized_value)
75
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
76
76
  rescue GraphQL::UnauthorizedError => auth_err
77
77
  @authorization_error = auth_err
78
78
  end
@@ -83,7 +83,7 @@ module GraphQL
83
83
  else
84
84
  create_result
85
85
  end
86
- rescue GraphQL::Error => err
86
+ rescue GraphQL::RuntimeError => err
87
87
  @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
88
88
  end
89
89
 
@@ -23,9 +23,9 @@ module GraphQL
23
23
  end
24
24
 
25
25
  def resolve_type(type, object, query)
26
- query.current_trace.begin_resolve_type(@static_type, object, query.context)
26
+ query.current_trace.begin_resolve_type(type, object, query.context)
27
27
  resolved_type, _ignored_new_value = query.resolve_type(type, object)
28
- query.current_trace.end_resolve_type(@static_type, object, query.context, resolved_type)
28
+ query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
29
29
  resolved_type
30
30
  end
31
31
 
@@ -140,7 +140,18 @@ module GraphQL
140
140
  )]
141
141
  end
142
142
  when "subscription"
143
- raise ArgumentError, "TODO implement subscriptions"
143
+ if !query.subscription_update?
144
+ schema.subscriptions.initialize_subscriptions(query)
145
+ end
146
+ isolated_steps[0] << SelectionsStep.new(
147
+ parent_type: root_type,
148
+ selections: selected_operation.selections,
149
+ objects: [root_value],
150
+ results: [data],
151
+ path: EmptyObjects::EMPTY_ARRAY,
152
+ runner: self,
153
+ query: query,
154
+ )
144
155
  else
145
156
  raise ArgumentError, "Unhandled operation type: #{operation.operation_type.inspect}"
146
157
  end
@@ -165,6 +176,10 @@ module GraphQL
165
176
 
166
177
  queries.each_with_index.map do |query, idx|
167
178
  result = results[idx]
179
+ if query.subscription?
180
+ @schema.subscriptions.finish_subscriptions(query)
181
+ end
182
+
168
183
  fin_result = if query.context.errors.empty?
169
184
  result
170
185
  else
@@ -184,7 +199,8 @@ module GraphQL
184
199
  res_h
185
200
  end
186
201
 
187
- GraphQL::Query::Result.new(query: query, values: fin_result)
202
+ query.result_values = fin_result
203
+ query.result
188
204
  end
189
205
  end
190
206
  ensure
@@ -241,16 +257,17 @@ module GraphQL
241
257
  paths_to_check.compact! # root-level auth errors currently come without a path
242
258
  # TODO dry with above?
243
259
  # This is also where a query-level "Step" would be used?
244
- selected_operation = query.document.definitions.first # TODO pick a selected operation
245
- root_type = case selected_operation.operation_type
246
- when nil, "query"
247
- query.schema.query
248
- when "mutation"
249
- query.schema.mutation
250
- when "subscription"
251
- raise "Not implemented yet, TODO"
260
+ if (selected_operation = query.selected_operation)
261
+ root_type = case selected_operation.operation_type
262
+ when nil, "query"
263
+ query.schema.query
264
+ when "mutation"
265
+ query.schema.mutation
266
+ when "subscription"
267
+ query.schema.subscription
268
+ end
269
+ check_object_result(query, data, root_type, selected_operation.selections, [], [], paths_to_check)
252
270
  end
253
- check_object_result(query, data, root_type, selected_operation.selections, [], [], paths_to_check)
254
271
  end
255
272
 
256
273
  def check_object_result(query, result_h, static_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
@@ -269,22 +286,26 @@ module GraphQL
269
286
  if (result_type_non_null = result_type.non_null?)
270
287
  result_type = result_type.of_type
271
288
  end
289
+
272
290
  new_result_value = if result_value.is_a?(GraphQL::Error)
273
291
  result_value.path = current_result_path.dup
274
- nil
292
+ result_value.assign_graphql_result(query, result_h, key)
293
+ result_h.key?(key) ? result_h[key] : :unassigned
275
294
  else
276
295
  if result_type.list?
277
296
  check_list_result(query, result_value, result_type.of_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
278
- elsif result_type.kind.leaf?
279
- result_value
280
- else
297
+ elsif !result_type.kind.leaf?
281
298
  check_object_result(query, result_value, result_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
299
+ else
300
+ result_value
282
301
  end
283
302
  end
284
303
 
285
304
  if new_result_value.nil? && result_type_non_null
286
305
  return nil
287
- else
306
+ elsif :unassigned.equal?(new_result_value)
307
+ # Do nothing
308
+ elsif !new_result_value.equal?(result_value)
288
309
  result_h[key] = new_result_value
289
310
  end
290
311
  end
@@ -317,24 +338,25 @@ module GraphQL
317
338
  end
318
339
 
319
340
  new_invalid_null = false
320
- result_arr.map!.with_index do |result_item, idx|
341
+ result_arr.each_with_index do |result_item, idx|
321
342
  current_result_path << idx
322
343
  new_result = if result_item.is_a?(GraphQL::Error)
323
344
  result_item.path = current_result_path.dup
324
- nil
345
+ result_item.assign_graphql_result(query, result_arr, idx)
346
+ result_arr[idx]
325
347
  elsif inner_type.list?
326
348
  check_list_result(query, result_item, inner_type.of_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
327
- elsif inner_type.kind.leaf?
328
- result_item
329
- else
349
+ elsif !inner_type.kind.leaf?
330
350
  check_object_result(query, result_item, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
351
+ else
352
+ result_item
331
353
  end
332
354
 
333
355
  if new_result.nil? && inner_type_non_null
334
356
  new_invalid_null = true
335
- nil
336
- else
337
- new_result
357
+ result_arr[idx] = nil
358
+ elsif !new_result.equal?(result_item)
359
+ result_arr[idx] = new_result
338
360
  end
339
361
  ensure
340
362
  current_result_path.pop
@@ -52,7 +52,7 @@ module GraphQL
52
52
 
53
53
  def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
54
54
  queries = query_options.map do |opts|
55
- case opts
55
+ query = case opts
56
56
  when Hash
57
57
  schema.query_class.new(schema, nil, **opts)
58
58
  when GraphQL::Query, GraphQL::Query::Partial
@@ -60,6 +60,8 @@ module GraphQL
60
60
  else
61
61
  raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
62
62
  end
63
+ query.context[:__graphql_execute_next] = true
64
+ query
63
65
  end
64
66
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
65
67
  runner = Runner.new(multiplex, **schema.execution_next_options)
@@ -10,10 +10,13 @@ require "graphql/execution/errors"
10
10
  module GraphQL
11
11
  module Execution
12
12
  # @api private
13
- class Skip < GraphQL::Error; end
13
+ class Skip < GraphQL::RuntimeError
14
+ attr_accessor :path
15
+ def ast_nodes=(_ignored); end
14
16
 
15
- # Just a singleton for implementing {Query::Context#skip}
16
- # @api private
17
- SKIP = Skip.new
17
+ def assign_graphql_result(query, result_data, key)
18
+ result_data.delete(key)
19
+ end
20
+ end
18
21
  end
19
22
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  class ExecutionError < GraphQL::RuntimeError
7
7
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
8
  def ast_node
9
- ast_nodes.first
9
+ ast_nodes&.first
10
10
  end
11
11
 
12
12
  def ast_node=(new_node)
@@ -36,6 +36,10 @@ module GraphQL
36
36
  super(message)
37
37
  end
38
38
 
39
+ def assign_graphql_result(query, result_data, key)
40
+ result_data[key] = nil
41
+ end
42
+
39
43
  # @return [Hash] An entry for the response's "errors" key
40
44
  def to_h
41
45
  hash = {
@@ -112,7 +112,7 @@ module GraphQL
112
112
  # Return this value to tell the runtime
113
113
  # to exclude this field from the response altogether
114
114
  def skip
115
- GraphQL::Execution::SKIP
115
+ GraphQL::Execution::Skip.new
116
116
  end
117
117
 
118
118
  # Add error at query-level.
@@ -665,10 +665,9 @@ module GraphQL
665
665
  end
666
666
 
667
667
  def authorizes?(context)
668
- method(:authorized?).owner != GraphQL::Schema::Field || (
669
- (args = context.types.arguments(self)) &&
670
- (args.any? { |a| a.authorizes?(context) })
671
- )
668
+ method(:authorized?).owner != GraphQL::Schema::Field ||
669
+ ((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
670
+ (@resolver_class&.authorizes?(context)) || false
672
671
  end
673
672
 
674
673
  def authorized?(object, args, context)
@@ -19,7 +19,7 @@ module GraphQL
19
19
  end
20
20
 
21
21
  def to_type_signature
22
- "[#{@of_type.to_type_signature}]"
22
+ @type_signature ||= -"[#{@of_type.to_type_signature}]"
23
23
  end
24
24
 
25
25
  # This is for introspection, where it's expected the name will be `null`
@@ -150,10 +150,14 @@ module GraphQL
150
150
 
151
151
  def global_id_field(field_name, **kwargs)
152
152
  type = self
153
- field field_name, "ID", **kwargs, null: false
153
+ field field_name, "ID", **kwargs, null: false, resolve_each: true
154
154
  define_method(field_name) do
155
155
  context.schema.id_from_object(object, type, context)
156
156
  end
157
+
158
+ define_singleton_method(field_name) do |object, context|
159
+ context.schema.id_from_object(object, type, context)
160
+ end
157
161
  end
158
162
 
159
163
  # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
@@ -24,7 +24,7 @@ module GraphQL
24
24
  end
25
25
 
26
26
  def to_type_signature
27
- "#{@of_type.to_type_signature}!"
27
+ @type_signature ||= -"#{@of_type.to_type_signature}!"
28
28
  end
29
29
 
30
30
  def inspect