graphql 2.6.1 → 2.6.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dataloader.rb +1 -1
  3. data/lib/graphql/execution/field_resolve_step.rb +165 -65
  4. data/lib/graphql/execution/finalize.rb +18 -7
  5. data/lib/graphql/execution/input_values.rb +110 -38
  6. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  7. data/lib/graphql/execution/load_argument_step.rb +35 -3
  8. data/lib/graphql/execution/next.rb +20 -12
  9. data/lib/graphql/execution/prepare_object_step.rb +18 -5
  10. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  11. data/lib/graphql/execution/runner.rb +64 -29
  12. data/lib/graphql/execution/selections_step.rb +1 -1
  13. data/lib/graphql/execution.rb +8 -1
  14. data/lib/graphql/execution_error.rb +6 -12
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/schema_type.rb +6 -2
  17. data/lib/graphql/language/lexer.rb +1 -1
  18. data/lib/graphql/language/parser.rb +1 -1
  19. data/lib/graphql/pagination/connections.rb +1 -3
  20. data/lib/graphql/query.rb +2 -2
  21. data/lib/graphql/schema/argument.rb +2 -2
  22. data/lib/graphql/schema/directive/feature.rb +4 -0
  23. data/lib/graphql/schema/directive/transform.rb +20 -0
  24. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  25. data/lib/graphql/schema/input_object.rb +4 -0
  26. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  27. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  28. data/lib/graphql/schema/resolver.rb +0 -7
  29. data/lib/graphql/schema/subscription.rb +53 -8
  30. data/lib/graphql/schema/timeout.rb +2 -2
  31. data/lib/graphql/schema/visibility/visit.rb +1 -1
  32. data/lib/graphql/schema.rb +30 -9
  33. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +6 -0
  34. data/lib/graphql/subscriptions/event.rb +0 -1
  35. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  36. data/lib/graphql/version.rb +1 -1
  37. metadata +3 -2
@@ -38,6 +38,7 @@ module GraphQL
38
38
  def argument_values(owner_defn, argument_nodes, field_resolve_step)
39
39
  arg_defns = @query.types.arguments(owner_defn)
40
40
  argument_values = {}
41
+ errors = nil
41
42
 
42
43
  arg_defns.each do |argument_definition|
43
44
  arg_ruby_key = argument_definition.keyword
@@ -52,11 +53,12 @@ module GraphQL
52
53
  arg_value = value_from_ast(arg_node.value, argument_definition.type)
53
54
  argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step)
54
55
  end
56
+ rescue GraphQL::RuntimeError => exec_err
57
+ errors ||= []
58
+ errors << exec_err
55
59
  end
56
60
 
57
- argument_values
58
- rescue GraphQL::ExecutionError => exec_err
59
- exec_err
61
+ return argument_values, errors
60
62
  end
61
63
 
62
64
  private
@@ -82,20 +84,43 @@ module GraphQL
82
84
  elsif type.kind.input_object?
83
85
  coerced_obj = {}
84
86
 
85
- @query.types.arguments(type).each do |arg|
86
- arg_key = arg.keyword
87
- if value.key?(arg.graphql_name)
88
- arg_value = value[arg.graphql_name]
89
- elsif value.key?(sym_name = arg.graphql_name.to_sym)
90
- arg_value = value[sym_name]
91
- elsif arg.default_value?
92
- coerced_obj[arg_key] = arg.default_value
93
- next
94
- else
95
- next
96
- end
87
+ if value.is_a?(Hash)
88
+ @query.types.arguments(type).each do |arg|
89
+ arg_key = arg.keyword
90
+ if value.key?(arg.graphql_name)
91
+ arg_value = value[arg.graphql_name]
92
+ elsif value.key?(sym_name = arg.graphql_name.to_sym)
93
+ arg_value = value[sym_name]
94
+ elsif arg.default_value?
95
+ coerced_obj[arg_key] = arg.default_value
96
+ next
97
+ else
98
+ next
99
+ end
100
+
101
+ if arg_value.nil? && arg.replace_null_with_default?
102
+ arg_value = arg.default_value
103
+ end
97
104
 
98
- coerced_obj[arg_key] = variable_value(arg_value, arg.type)
105
+ coerced_obj[arg_key] = variable_value(arg_value, arg.type)
106
+ end
107
+ else
108
+ @query.types.arguments(type).each do |arg|
109
+ arg_key = arg.keyword
110
+ arg_name = arg.graphql_name
111
+ if (v_node = value.arguments.find { |a| a.name == arg_name }) # rubocop:disable Development/ContextIsPassedCop
112
+ arg_value = v_node.value
113
+ coerced_obj[arg_key] = if arg_value.nil? && arg.replace_null_with_default?
114
+ arg.default_value
115
+ else
116
+ variable_value(arg_value, arg.type)
117
+ end
118
+ elsif arg.default_value?
119
+ coerced_obj[arg_key] = arg.default_value
120
+ else
121
+ # Nothing
122
+ end
123
+ end
99
124
  end
100
125
 
101
126
  coerced_obj
@@ -109,11 +134,18 @@ module GraphQL
109
134
  def argument_value(argument_values, argument_key, argument_definition, arg_value, override_type, field_resolve_step)
110
135
  treat_as_type = override_type || argument_definition.type
111
136
  if treat_as_type.non_null?
137
+ if arg_value.nil?
138
+ treat_as_type.coerce_input(arg_value, @query.context)
139
+ end
112
140
  treat_as_type = treat_as_type.of_type
113
141
  end
114
142
 
143
+ if arg_value.nil? && argument_definition.replace_null_with_default?
144
+ arg_value = argument_definition.default_value
145
+ end
146
+
115
147
  if treat_as_type.kind.list? && !arg_value.nil?
116
- inner_t = treat_as_type.unwrap
148
+ inner_t = treat_as_type.of_type
117
149
  arg_value = if arg_value.is_a?(Array)
118
150
  values = Array.new(arg_value.size)
119
151
  arg_value.each_with_index { |inner_v, idx| argument_value(values, idx, argument_definition, inner_v, inner_t, field_resolve_step)}
@@ -150,7 +182,7 @@ module GraphQL
150
182
  arg_value = begin
151
183
  argument_definition.prepare_value(nil, arg_value, context: @query.context)
152
184
  rescue StandardError => err
153
- @runner.schema.handle_or_reraise(@query.context, err)
185
+ @runner.schema.handle_or_reraise(@query.context, err, object: nil, arguments: argument_values, field: field_resolve_step&.field_definition)
154
186
  end
155
187
  end
156
188
 
@@ -205,8 +237,6 @@ module GraphQL
205
237
  nil
206
238
  elsif value_node.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
207
239
  variable_values[value_node.name]
208
- elsif value_node.is_a?(GraphQL::Language::Nodes::NullValue)
209
- nil
210
240
  elsif type.list?
211
241
  inner_type = type.of_type
212
242
  if value_node.is_a?(Array)
@@ -214,37 +244,53 @@ module GraphQL
214
244
  value_from_ast(inner_value_node, inner_type)
215
245
  end
216
246
  coerced_items.freeze
247
+ elsif value_node.is_a?(Language::Nodes::NullValue)
248
+ nil
217
249
  else
218
250
  item_value = value_from_ast(value_node, inner_type)
219
251
  [item_value].freeze
220
252
  end
221
-
222
253
  elsif type.kind.input_object?
223
254
  coerced_obj = {}
224
- arg_nodes_by_name = value_node.arguments.each_with_object({}) do |arg_node, acc| # rubocop:disable Development/ContextIsPassedCop
225
- acc[arg_node.name] = arg_node
226
- end
227
-
228
- @query.types.arguments(type).each do |arg|
229
- arg_node = arg_nodes_by_name[arg.graphql_name]
230
- arg_key = arg.keyword
231
- if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name))
232
- if arg.default_value?
233
- coerced_obj[arg_key] = arg.default_value
255
+ # TODO manually handle NullValue here?
256
+ if value_node.is_a?(Hash)
257
+ @query.types.arguments(type).each do |arg|
258
+ arg_value = value_node[arg.keyword]
259
+ arg_key = arg.keyword
260
+ if arg_value.nil?
261
+ if arg.default_value?
262
+ coerced_obj[arg_key] = arg.default_value
263
+ end
264
+ next
234
265
  end
235
- next
266
+
267
+ coerced_obj[arg_key] = value_from_ast(arg_value, arg.type)
268
+ end
269
+ else
270
+ arg_nodes_by_name = value_node.arguments.each_with_object({}) do |arg_node, acc| # rubocop:disable Development/ContextIsPassedCop
271
+ acc[arg_node.name] = arg_node
236
272
  end
237
273
 
238
- arg_value = value_from_ast(arg_node.value, arg.type)
239
- coerced_obj[arg_key] = arg_value
274
+ @query.types.arguments(type).each do |arg|
275
+ arg_node = arg_nodes_by_name[arg.graphql_name]
276
+ arg_key = arg.keyword
277
+ if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name))
278
+ if arg.default_value?
279
+ coerced_obj[arg_key] = arg.default_value
280
+ end
281
+ next
282
+ end
283
+
284
+ arg_value = value_from_ast(arg_node.value, arg.type)
285
+ coerced_obj[arg_key] = arg_value
286
+ end
240
287
  end
241
288
 
289
+
242
290
  coerced_obj
243
291
  elsif type.kind.leaf?
244
- if type.kind.enum?
245
- if value_node.is_a?(GraphQL::Language::Nodes::Enum)
246
- value_node = value_node.name
247
- end
292
+ if value_node.is_a?(Language::Nodes::AbstractNode) || value_node.is_a?(Array)
293
+ value_node = coerce_untyped_input(value_node)
248
294
  end
249
295
 
250
296
  begin
@@ -256,6 +302,32 @@ module GraphQL
256
302
  raise "Unexpected input type: #{type.to_type_signature}."
257
303
  end
258
304
  end
305
+
306
+ private
307
+
308
+ def coerce_untyped_input(input_value)
309
+ case input_value
310
+ when Language::Nodes::AbstractNode
311
+ case input_value
312
+ when Language::Nodes::NullValue
313
+ nil
314
+ when Language::Nodes::Enum
315
+ input_value.name
316
+ when Language::Nodes::InputObject
317
+ value_h = {}
318
+ input_value.arguments.each do |arg| # rubocop:disable Development/ContextIsPassedCop
319
+ value_h[arg.name] = coerce_untyped_input(arg.value)
320
+ end
321
+ value_h
322
+ else
323
+ raise "Unhandled untyped input AST node: #{input_value.class}"
324
+ end
325
+ when Array
326
+ input_value.map { |v| coerce_untyped_input(v) }
327
+ else
328
+ input_value
329
+ end
330
+ end
259
331
  end
260
332
  end
261
333
  end
@@ -198,7 +198,7 @@ module GraphQL
198
198
 
199
199
  def each_gathered_selections(response_hash)
200
200
  ordered_result_keys = []
201
- gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
201
+ gathered_selections = gather_selections(response_hash, response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
202
202
  ordered_result_keys.uniq!
203
203
  if gathered_selections.is_a?(Array)
204
204
  gathered_selections.each do |item|
@@ -209,15 +209,13 @@ module GraphQL
209
209
  end
210
210
  end
211
211
 
212
- def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
212
+ def gather_selections(graphql_response, owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
213
213
  selections.each do |node|
214
- # Skip gathering this if the directive says so
215
- if !directives_include?(node, owner_object, owner_type)
216
- next
217
- end
218
-
219
214
  if node.is_a?(GraphQL::Language::Nodes::Field)
220
215
  response_key = node.alias || node.name
216
+ if !directives_include?(node, owner_object, owner_type, graphql_response, response_key)
217
+ next
218
+ end
221
219
  ordered_result_keys << response_key
222
220
  selections = selections_by_name[response_key]
223
221
  # if there was already a selection of this field,
@@ -234,6 +232,9 @@ module GraphQL
234
232
  selections_by_name[response_key] = node
235
233
  end
236
234
  else
235
+ if !directives_include?(node, owner_object, owner_type, graphql_response, nil)
236
+ next
237
+ end
237
238
  # This is an InlineFragment or a FragmentSpread
238
239
  if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
239
240
  next_selections = {}
@@ -255,14 +256,14 @@ module GraphQL
255
256
  type_defn = query.types.type(node.type.name)
256
257
 
257
258
  if query.types.possible_types(type_defn).include?(owner_type)
258
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
259
+ result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
259
260
  if !result.equal?(next_selections)
260
261
  selections_to_run = result
261
262
  end
262
263
  end
263
264
  else
264
265
  # it's an untyped fragment, definitely continue
265
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
266
+ result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
266
267
  if !result.equal?(next_selections)
267
268
  selections_to_run = result
268
269
  end
@@ -271,7 +272,7 @@ module GraphQL
271
272
  fragment_def = query.fragments[node.name]
272
273
  type_defn = query.types.type(fragment_def.type.name)
273
274
  if query.types.possible_types(type_defn).include?(owner_type)
274
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
275
+ result = gather_selections(graphql_response, owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
275
276
  if !result.equal?(next_selections)
276
277
  selections_to_run = result
277
278
  end
@@ -339,7 +340,7 @@ module GraphQL
339
340
  end
340
341
  field_name = ast_node.name
341
342
  owner_type = selections_result.graphql_result_type
342
- field_defn = query.types.field(owner_type, field_name)
343
+ field_defn = query.types.field(owner_type, field_name) || raise(GraphQL::Error, "No field definition found for #{owner_type.graphql_name}.#{field_name} (at #{ast_node.position})")
343
344
 
344
345
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
345
346
  runtime_state = get_current_runtime_state
@@ -579,7 +580,7 @@ module GraphQL
579
580
  value.path ||= current_path
580
581
  value.ast_node ||= ast_node
581
582
  context.errors << value
582
- if selection_result
583
+ if selection_result && result_name
583
584
  set_result(selection_result, result_name, nil, false, is_non_null)
584
585
  end
585
586
  end
@@ -856,11 +857,31 @@ module GraphQL
856
857
  end
857
858
 
858
859
  # Check {Schema::Directive.include?} for each directive that's present
859
- def directives_include?(node, graphql_object, parent_type)
860
+ def directives_include?(node, graphql_object, parent_type, selection_result, extra_path_part)
860
861
  node.directives.each do |dir_node|
861
862
  dir_defn = @schema_directives.fetch(dir_node.name)
862
- args = arguments(graphql_object, dir_defn, dir_node)
863
- if !dir_defn.include?(graphql_object, args, context)
863
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
864
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
865
+ begin
866
+ dir_defn.validate!(raw_dir_args, context)
867
+ rescue GraphQL::ExecutionError => err
868
+ raw_dir_args = err
869
+ end
870
+ end
871
+
872
+ if extra_path_part && raw_dir_args.is_a?(GraphQL::ExecutionError)
873
+ raw_dir_args.path = current_path + [extra_path_part]
874
+ end
875
+
876
+ dir_args = continue_value(
877
+ raw_dir_args, # value
878
+ nil, # field
879
+ false, # is_non_null
880
+ dir_node, # ast_node
881
+ nil, # result_name
882
+ selection_result
883
+ )
884
+ if dir_args == HALT || !dir_defn.include?(graphql_object, dir_args, context)
864
885
  return false
865
886
  end
866
887
  end
@@ -10,10 +10,29 @@ module GraphQL
10
10
  @argument_definition = argument_definition
11
11
  @argument_key = argument_key
12
12
  @loaded_value = nil
13
+ @is_authorized = true
13
14
  end
14
15
 
15
16
  def value
16
- @loaded_value = @field_resolve_step.sync(@loaded_value)
17
+ schema = @field_resolve_step.runner.schema
18
+ @loaded_value = schema.sync_lazy(@loaded_value)
19
+ assign_value
20
+ rescue GraphQL::UnauthorizedError => auth_err
21
+ @is_authorized = false
22
+ schema.unauthorized_object(auth_err)
23
+ rescue GraphQL::RuntimeError => err
24
+ @loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
25
+ err.unsubscribed_result
26
+ else
27
+ err
28
+ end
29
+ assign_value
30
+ rescue StandardError => stderr
31
+ begin
32
+ @field_resolve_step.selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
33
+ rescue GraphQL::ExecutionError => ex_err
34
+ @loaded_value = ex_err
35
+ end
17
36
  assign_value
18
37
  end
19
38
 
@@ -22,6 +41,7 @@ module GraphQL
22
41
  @loaded_value = begin
23
42
  @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
24
43
  rescue GraphQL::UnauthorizedError => auth_err
44
+ @is_authorized = false
25
45
  context.schema.unauthorized_object(auth_err)
26
46
  end
27
47
  if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
@@ -30,11 +50,16 @@ module GraphQL
30
50
  assign_value
31
51
  end
32
52
  rescue GraphQL::RuntimeError => err
33
- @loaded_value = err
53
+ @loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
54
+ @is_authorized = false
55
+ err.unsubscribed_result
56
+ else
57
+ err
58
+ end
34
59
  assign_value
35
60
  rescue StandardError => stderr
36
61
  @loaded_value = begin
37
- context.query.handle_or_reraise(stderr)
62
+ context.query.handle_or_reraise(stderr, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: nil) # rubocop:disable Development/ContextIsPassedCop
38
63
  rescue GraphQL::ExecutionError => ex_err
39
64
  ex_err
40
65
  end
@@ -47,6 +72,13 @@ module GraphQL
47
72
  if @loaded_value.is_a?(GraphQL::RuntimeError)
48
73
  @loaded_value.path = @field_resolve_step.path
49
74
  @field_resolve_step.arguments = @loaded_value
75
+ elsif @is_authorized == false
76
+ # An unauthorized_object hook ate the error
77
+ @field_resolve_step.arguments = EmptyObjects::EMPTY_HASH
78
+ field_pending_steps = @field_resolve_step.pending_steps
79
+ field_pending_steps.clear
80
+ @field_resolve_step.build_errors_result(nil, nil)
81
+ return
50
82
  else
51
83
  query = @field_resolve_step.selections_step.query
52
84
  query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context)
@@ -4,15 +4,27 @@ require "graphql/execution/input_values"
4
4
  require "graphql/execution/field_resolve_step"
5
5
  require "graphql/execution/finalize"
6
6
  require "graphql/execution/load_argument_step"
7
+ require "graphql/execution/resolve_type_step"
7
8
  require "graphql/execution/runner"
8
9
  require "graphql/execution/selections_step"
9
10
  module GraphQL
10
11
  module Execution
11
12
  module Finalizer
12
13
  attr_accessor :path
14
+
13
15
  def finalize_graphql_result(query, result_data, result_key)
14
- raise RequiredImplementationMissingError
16
+ raise RequiredImplementationMissingError, "#{self.class} must implement #finalize_graphql_result(query, result_data, result_key)\n\nresult_data: #{result_data}\nresult_key: #{result_key.inspect}"
17
+ end
18
+
19
+ def ast_node
20
+ ast_nodes&.first
21
+ end
22
+
23
+ def ast_node=(new_node)
24
+ @ast_nodes = [new_node]
15
25
  end
26
+
27
+ attr_accessor :ast_nodes
16
28
  end
17
29
 
18
30
  module HaltExecution
@@ -26,7 +38,7 @@ module GraphQL
26
38
 
27
39
  module Next
28
40
  module SchemaExtension
29
- def execute_next(query_str = nil, context: nil, document: nil, operation_name: nil, variables: nil, root_value: nil, validate: true, visibility_profile: nil)
41
+ def execute_next(query_str = nil, query: nil, subscription_topic: nil, context: nil, document: nil, operation_name: nil, variables: nil, warden: nil, root_value: nil, validate: true, visibility_profile: nil)
30
42
  multiplex_context = if context
31
43
  {
32
44
  backtrace: context[:backtrace],
@@ -39,7 +51,8 @@ module GraphQL
39
51
  {}
40
52
  end
41
53
  query_opts = {
42
- query: query_str,
54
+ query: query || query_str,
55
+ subscription_topic: subscription_topic,
43
56
  document: document,
44
57
  context: context,
45
58
  validate: validate,
@@ -47,6 +60,7 @@ module GraphQL
47
60
  root_value: root_value,
48
61
  operation_name: operation_name,
49
62
  visibility_profile: visibility_profile,
63
+ warden: warden,
50
64
  }
51
65
  m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil)
52
66
  m_results[0]
@@ -55,17 +69,11 @@ module GraphQL
55
69
  def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity)
56
70
  Next.run_all(self, query_options, context: context, max_complexity: max_complexity)
57
71
  end
58
-
59
- def execution_next_options
60
- @execution_next_options || find_inherited_value(:execution_next_options, EmptyObjects::EMPTY_HASH)
61
- end
62
-
63
- attr_writer :execution_next_options
64
72
  end
65
73
 
66
- def self.use(schema, authorization: true)
74
+ def self.use(schema, as_default: false)
67
75
  schema.extend(SchemaExtension)
68
- schema.execution_next_options = { authorization: authorization }
76
+ schema.default_execution_next(as_default)
69
77
  end
70
78
 
71
79
  def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
@@ -82,7 +90,7 @@ module GraphQL
82
90
  query
83
91
  end
84
92
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
85
- runner = Runner.new(multiplex, **schema.execution_next_options)
93
+ runner = Runner.new(multiplex)
86
94
  runner.execute
87
95
  end
88
96
  end
@@ -28,7 +28,11 @@ module GraphQL
28
28
  ctx = @field_resolve_step.selections_step.query.context
29
29
  st = @field_resolve_step.static_type
30
30
  ctx.query.current_trace.begin_resolve_type(st, @object, ctx)
31
- @resolved_type, _ignored_value = @field_resolve_step.sync(@resolved_type)
31
+ @resolved_type, new_value = @field_resolve_step.sync(@resolved_type)
32
+ ResolveTypeStep.assert_valid_resolved_type(st, @resolved_type, new_value, @field_resolve_step)
33
+ if new_value
34
+ @object = new_value
35
+ end
32
36
  ctx.query.current_trace.end_resolve_type(st, @object, ctx, @resolved_type)
33
37
  end
34
38
  @runner.add_step(self)
@@ -39,10 +43,12 @@ module GraphQL
39
43
  when :resolve_type
40
44
  static_type = @field_resolve_step.static_type
41
45
  if static_type.kind.abstract?
42
- ctx = @field_resolve_step.selections_step.query.context
43
- ctx.query.current_trace.begin_resolve_type(static_type, @object, ctx)
44
- @resolved_type, _ignored_value = @runner.schema.resolve_type(static_type, @object, ctx)
45
- ctx.query.current_trace.end_resolve_type(static_type, @object, ctx, @resolved_type)
46
+ query = @field_resolve_step.selections_step.query
47
+ @resolved_type, new_value = ResolveTypeStep.resolve_type(static_type, @object, query)
48
+ if new_value
49
+ ResolveTypeStep.assert_valid_resolved_type(static_type, @resolved_type, new_value, @field_resolve_step)
50
+ @object = new_value
51
+ end
46
52
  else
47
53
  @resolved_type = static_type
48
54
  end
@@ -85,6 +91,13 @@ module GraphQL
85
91
  end
86
92
  rescue GraphQL::RuntimeError => err
87
93
  @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
94
+ rescue StandardError => err
95
+ query ||= @field_resolve_step.selections_step.query
96
+ begin
97
+ query.handle_or_reraise(err, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: @object) # rubocop:disable Development/ContextIsPassedCop
98
+ rescue GraphQL::RuntimeError => err
99
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
100
+ end
88
101
  end
89
102
 
90
103
  def create_result
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class ResolveTypeStep
5
+ def self.resolve_type(type, object, query)
6
+ query.current_trace.begin_resolve_type(type, object, query.context)
7
+ resolved_type_response = query.resolve_type(type, object)
8
+ resolved_type = if resolved_type_response.is_a?(Array)
9
+ resolved_type_response.first
10
+ else
11
+ resolved_type_response
12
+ end
13
+ query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
14
+ resolved_type_response
15
+ end
16
+
17
+ def self.assert_valid_resolved_type(abstract_type, resolved_type, object, field_resolution_step, query: field_resolution_step.selections_step.query)
18
+ possible_types = query.types.possible_types(abstract_type)
19
+ if !possible_types.include?(resolved_type)
20
+ err_class = abstract_type::UnresolvedTypeError
21
+ type_error = err_class.new(object, field_resolution_step.field_definition, abstract_type, resolved_type, possible_types)
22
+ query.schema.type_error(type_error, query.context)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end