graphql 2.0.16 → 2.0.21

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/visitor.rb +42 -35
  3. data/lib/graphql/analysis/ast.rb +2 -2
  4. data/lib/graphql/backtrace/trace.rb +96 -0
  5. data/lib/graphql/backtrace/tracer.rb +1 -1
  6. data/lib/graphql/backtrace.rb +6 -1
  7. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  8. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -3
  9. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  10. data/lib/graphql/execution/interpreter/runtime.rb +264 -211
  11. data/lib/graphql/execution/interpreter.rb +15 -10
  12. data/lib/graphql/execution/lazy.rb +6 -12
  13. data/lib/graphql/execution/multiplex.rb +2 -1
  14. data/lib/graphql/filter.rb +7 -2
  15. data/lib/graphql/introspection/directive_type.rb +2 -2
  16. data/lib/graphql/introspection/field_type.rb +1 -1
  17. data/lib/graphql/introspection/schema_type.rb +2 -2
  18. data/lib/graphql/introspection/type_type.rb +5 -5
  19. data/lib/graphql/language/document_from_schema_definition.rb +25 -9
  20. data/lib/graphql/language/lexer.rb +216 -1505
  21. data/lib/graphql/language/nodes.rb +66 -40
  22. data/lib/graphql/language/parser.rb +509 -491
  23. data/lib/graphql/language/parser.y +43 -38
  24. data/lib/graphql/language/visitor.rb +191 -83
  25. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  26. data/lib/graphql/pagination/connection.rb +5 -5
  27. data/lib/graphql/query/context.rb +62 -31
  28. data/lib/graphql/query/null_context.rb +1 -1
  29. data/lib/graphql/query.rb +22 -5
  30. data/lib/graphql/schema/argument.rb +7 -13
  31. data/lib/graphql/schema/build_from_definition.rb +15 -3
  32. data/lib/graphql/schema/directive.rb +12 -2
  33. data/lib/graphql/schema/enum.rb +24 -17
  34. data/lib/graphql/schema/enum_value.rb +2 -3
  35. data/lib/graphql/schema/field.rb +68 -57
  36. data/lib/graphql/schema/field_extension.rb +1 -4
  37. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  38. data/lib/graphql/schema/interface.rb +0 -10
  39. data/lib/graphql/schema/late_bound_type.rb +2 -0
  40. data/lib/graphql/schema/member/base_dsl_methods.rb +17 -14
  41. data/lib/graphql/schema/member/has_arguments.rb +105 -58
  42. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  43. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  44. data/lib/graphql/schema/member/has_directives.rb +15 -10
  45. data/lib/graphql/schema/member/has_fields.rb +95 -38
  46. data/lib/graphql/schema/member/has_interfaces.rb +49 -8
  47. data/lib/graphql/schema/member/has_validators.rb +32 -6
  48. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  49. data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
  50. data/lib/graphql/schema/object.rb +2 -4
  51. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  52. data/lib/graphql/schema/resolver.rb +4 -4
  53. data/lib/graphql/schema/timeout.rb +24 -28
  54. data/lib/graphql/schema/validator.rb +1 -1
  55. data/lib/graphql/schema/warden.rb +29 -5
  56. data/lib/graphql/schema.rb +76 -25
  57. data/lib/graphql/static_validation/literal_validator.rb +15 -1
  58. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  59. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  60. data/lib/graphql/static_validation/validator.rb +1 -1
  61. data/lib/graphql/subscriptions/event.rb +2 -7
  62. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  63. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  64. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  65. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  66. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  67. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  68. data/lib/graphql/tracing/notifications_trace.rb +42 -0
  69. data/lib/graphql/tracing/platform_trace.rb +109 -0
  70. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  71. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  72. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  73. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  74. data/lib/graphql/tracing/scout_trace.rb +72 -0
  75. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  76. data/lib/graphql/tracing/trace.rb +75 -0
  77. data/lib/graphql/tracing.rb +16 -39
  78. data/lib/graphql/type_kinds.rb +6 -3
  79. data/lib/graphql/types/relay/base_connection.rb +1 -1
  80. data/lib/graphql/types/relay/connection_behaviors.rb +24 -6
  81. data/lib/graphql/types/relay/edge_behaviors.rb +16 -6
  82. data/lib/graphql/types/relay/node_behaviors.rb +7 -1
  83. data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
  84. data/lib/graphql/types/relay.rb +0 -1
  85. data/lib/graphql/types/string.rb +1 -1
  86. data/lib/graphql/version.rb +1 -1
  87. data/lib/graphql.rb +16 -9
  88. metadata +34 -9
  89. data/lib/graphql/language/lexer.rl +0 -280
  90. data/lib/graphql/types/relay/default_relay.rb +0 -27
@@ -8,6 +8,18 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+ class CurrentState
12
+ def initialize
13
+ @current_object = nil
14
+ @current_field = nil
15
+ @current_arguments = nil
16
+ @current_result_name = nil
17
+ @current_result = nil
18
+ end
19
+
20
+ attr_accessor :current_result, :current_result_name,
21
+ :current_arguments, :current_field, :current_object
22
+ end
11
23
 
12
24
  module GraphQLResult
13
25
  def initialize(result_name, parent_result)
@@ -20,6 +32,15 @@ module GraphQL
20
32
  @graphql_metadata = nil
21
33
  end
22
34
 
35
+ def path
36
+ @path ||= build_path([])
37
+ end
38
+
39
+ def build_path(path_array)
40
+ graphql_result_name && path_array.unshift(graphql_result_name)
41
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
42
+ end
43
+
23
44
  attr_accessor :graphql_dead
24
45
  attr_reader :graphql_parent, :graphql_result_name
25
46
 
@@ -45,7 +66,7 @@ module GraphQL
45
66
 
46
67
  attr_accessor :graphql_merged_into
47
68
 
48
- def []=(key, value)
69
+ def set_leaf(key, value)
49
70
  # This is a hack.
50
71
  # Basically, this object is merged into the root-level result at some point.
51
72
  # But the problem is, some lazies are created whose closures retain reference to _this_
@@ -55,23 +76,27 @@ module GraphQL
55
76
  # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
56
77
  # Yowza.
57
78
  if (t = @graphql_merged_into)
58
- t[key] = value
79
+ t.set_leaf(key, value)
59
80
  end
60
81
 
61
- if value.respond_to?(:graphql_result_data)
62
- @graphql_result_data[key] = value.graphql_result_data
63
- # If we encounter some part of this response that requires metadata tracking,
64
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
65
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
66
- else
67
- @graphql_result_data[key] = value
68
- # keep this up-to-date if it's been initialized
69
- @graphql_metadata && @graphql_metadata[key] = value
70
- end
82
+ @graphql_result_data[key] = value
83
+ # keep this up-to-date if it's been initialized
84
+ @graphql_metadata && @graphql_metadata[key] = value
71
85
 
72
86
  value
73
87
  end
74
88
 
89
+ def set_child_result(key, value)
90
+ if (t = @graphql_merged_into)
91
+ t.set_child_result(key, value)
92
+ end
93
+ @graphql_result_data[key] = value.graphql_result_data
94
+ # If we encounter some part of this response that requires metadata tracking,
95
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
96
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
97
+ value
98
+ end
99
+
75
100
  def delete(key)
76
101
  @graphql_metadata && @graphql_metadata.delete(key)
77
102
  @graphql_result_data.delete(key)
@@ -92,6 +117,29 @@ module GraphQL
92
117
  def [](k)
93
118
  (@graphql_metadata || @graphql_result_data)[k]
94
119
  end
120
+
121
+ def merge_into(into_result)
122
+ self.each do |key, value|
123
+ case value
124
+ when GraphQLResultHash
125
+ next_into = into_result[key]
126
+ if next_into
127
+ value.merge_into(next_into)
128
+ else
129
+ into_result.set_child_result(key, value)
130
+ end
131
+ when GraphQLResultArray
132
+ # There's no special handling of arrays because currently, there's no way to split the execution
133
+ # of a list over several concurrent flows.
134
+ next_result.set_child_result(key, value)
135
+ else
136
+ # We have to assume that, since this passed the `fields_will_merge` selection,
137
+ # that the old and new values are the same.
138
+ into_result.set_leaf(key, value)
139
+ end
140
+ end
141
+ @graphql_merged_into = into_result
142
+ end
95
143
  end
96
144
 
97
145
  class GraphQLResultArray
@@ -114,19 +162,25 @@ module GraphQL
114
162
  @graphql_result_data.delete_at(delete_at_index)
115
163
  end
116
164
 
117
- def []=(idx, value)
165
+ def set_leaf(idx, value)
118
166
  if @skip_indices
119
167
  offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
120
168
  idx -= offset_by
121
169
  end
122
- if value.respond_to?(:graphql_result_data)
123
- @graphql_result_data[idx] = value.graphql_result_data
124
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
125
- else
126
- @graphql_result_data[idx] = value
127
- @graphql_metadata && @graphql_metadata[idx] = value
128
- end
170
+ @graphql_result_data[idx] = value
171
+ @graphql_metadata && @graphql_metadata[idx] = value
172
+ value
173
+ end
129
174
 
175
+ def set_child_result(idx, value)
176
+ if @skip_indices
177
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
178
+ idx -= offset_by
179
+ end
180
+ @graphql_result_data[idx] = value.graphql_result_data
181
+ # If we encounter some part of this response that requires metadata tracking,
182
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
183
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
130
184
  value
131
185
  end
132
186
 
@@ -148,18 +202,10 @@ module GraphQL
148
202
  # @return [GraphQL::Query::Context]
149
203
  attr_reader :context
150
204
 
151
- def thread_info
152
- info = Thread.current[:__graphql_runtime_info]
153
- if !info
154
- new_ti = {}
155
- info = Thread.current[:__graphql_runtime_info] = new_ti
156
- end
157
- info
158
- end
159
-
160
- def initialize(query:)
205
+ def initialize(query:, lazies_at_depth:)
161
206
  @query = query
162
207
  @dataloader = query.multiplex.dataloader
208
+ @lazies_at_depth = lazies_at_depth
163
209
  @schema = query.schema
164
210
  @context = query.context
165
211
  @multiplex_context = query.multiplex.context
@@ -208,8 +254,9 @@ module GraphQL
208
254
  root_operation = query.selected_operation
209
255
  root_op_type = root_operation.operation_type || "query"
210
256
  root_type = schema.root_type_for_operation(root_op_type)
211
- path = []
212
- set_all_interpreter_context(query.root_value, nil, nil, path)
257
+ st = get_current_runtime_state
258
+ st.current_object = query.root_value
259
+ st.current_result = @response
213
260
  object_proxy = authorized_new(root_type, query.root_value, context)
214
261
  object_proxy = schema.sync_lazy(object_proxy)
215
262
 
@@ -236,10 +283,12 @@ module GraphQL
236
283
  end
237
284
 
238
285
  @dataloader.append_job {
239
- set_all_interpreter_context(query.root_value, nil, nil, path)
286
+ st = get_current_runtime_state
287
+ st.current_object = query.root_value
288
+ st.current_result = selection_response
289
+
240
290
  call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
241
291
  evaluate_selections(
242
- path,
243
292
  object_proxy,
244
293
  root_type,
245
294
  root_op_type == "mutation",
@@ -253,32 +302,7 @@ module GraphQL
253
302
  end
254
303
  end
255
304
  end
256
- delete_interpreter_context(:current_path)
257
- delete_interpreter_context(:current_field)
258
- delete_interpreter_context(:current_object)
259
- delete_interpreter_context(:current_arguments)
260
- nil
261
- end
262
-
263
- # @return [void]
264
- def deep_merge_selection_result(from_result, into_result)
265
- from_result.each do |key, value|
266
- if !into_result.key?(key)
267
- into_result[key] = value
268
- else
269
- case value
270
- when GraphQLResultHash
271
- deep_merge_selection_result(value, into_result[key])
272
- else
273
- # We have to assume that, since this passed the `fields_will_merge` selection,
274
- # that the old and new values are the same.
275
- # There's no special handling of arrays because currently, there's no way to split the execution
276
- # of a list over several concurrent flows.
277
- into_result[key] = value
278
- end
279
- end
280
- end
281
- from_result.graphql_merged_into = into_result
305
+ delete_all_interpreter_context
282
306
  nil
283
307
  end
284
308
 
@@ -355,22 +379,25 @@ module GraphQL
355
379
  selections_to_run || selections_by_name
356
380
  end
357
381
 
358
- NO_ARGS = {}.freeze
382
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
359
383
 
360
384
  # @return [void]
361
- def evaluate_selections(path, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
362
- set_all_interpreter_context(owner_object, nil, nil, path)
385
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
386
+ st = get_current_runtime_state
387
+ st.current_object = owner_object
388
+ st.current_result_name = nil
389
+ st.current_result = selections_result
363
390
 
364
391
  finished_jobs = 0
365
392
  enqueued_jobs = gathered_selections.size
366
393
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
367
394
  @dataloader.append_job {
368
395
  evaluate_selection(
369
- path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
396
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
370
397
  )
371
398
  finished_jobs += 1
372
399
  if target_result && finished_jobs == enqueued_jobs
373
- deep_merge_selection_result(selections_result, target_result)
400
+ selections_result.merge_into(target_result)
374
401
  end
375
402
  }
376
403
  end
@@ -378,10 +405,8 @@ module GraphQL
378
405
  selections_result
379
406
  end
380
407
 
381
- attr_reader :progress_path
382
-
383
408
  # @return [void]
384
- def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
409
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
385
410
  return if dead_result?(selections_result)
386
411
  # As a performance optimization, the hash key will be a `Node` if
387
412
  # there's only one selection of the field. But if there are multiple
@@ -409,19 +434,22 @@ module GraphQL
409
434
  raise "Invariant: no field for #{owner_type}.#{field_name}"
410
435
  end
411
436
  end
412
- return_type = field_defn.type
413
437
 
414
- next_path = path + [result_name]
415
- next_path.freeze
438
+ return_type = field_defn.type
416
439
 
417
440
  # This seems janky, but we need to know
418
441
  # the field's return type at this path in order
419
442
  # to propagate `null`
420
- if return_type.non_null?
443
+ return_type_non_null = return_type.non_null?
444
+ if return_type_non_null
421
445
  (selections_result.graphql_non_null_field_names ||= []).push(result_name)
422
446
  end
423
447
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
424
- set_all_interpreter_context(nil, field_defn, nil, next_path)
448
+ st = get_current_runtime_state
449
+ st.current_field = field_defn
450
+ st.current_result = selections_result
451
+ st.current_result_name = result_name
452
+
425
453
  object = owner_object
426
454
 
427
455
  if is_introspection
@@ -431,26 +459,34 @@ module GraphQL
431
459
  total_args_count = field_defn.arguments(context).size
432
460
  if total_args_count == 0
433
461
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
434
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
462
+ if field_defn.extras.size == 0
463
+ evaluate_selection_with_resolved_keyword_args(
464
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
465
+ )
466
+ else
467
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
468
+ end
435
469
  else
436
- # TODO remove all arguments(...) usages?
437
470
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
438
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
471
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
439
472
  end
440
473
  end
441
474
  end
442
475
 
443
- def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
444
- return_type = field_defn.type
445
- after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
476
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
477
+ after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
446
478
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
447
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
479
+ continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
448
480
  next
449
481
  end
450
482
 
451
- kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
452
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
453
- NO_ARGS
483
+ kwarg_arguments = if field_defn.extras.empty?
484
+ if resolved_arguments.empty?
485
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
486
+ NO_ARGS
487
+ else
488
+ resolved_arguments.keyword_arguments
489
+ end
454
490
  else
455
491
  # Bundle up the extras, then make a new arguments instance
456
492
  # that includes the extras, too.
@@ -460,9 +496,9 @@ module GraphQL
460
496
  when :ast_node
461
497
  extra_args[:ast_node] = ast_node
462
498
  when :execution_errors
463
- extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
499
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
464
500
  when :path
465
- extra_args[:path] = next_path
501
+ extra_args[:path] = current_path
466
502
  when :lookahead
467
503
  if !field_ast_nodes
468
504
  field_ast_nodes = [ast_node]
@@ -489,61 +525,70 @@ module GraphQL
489
525
  resolved_arguments.keyword_arguments
490
526
  end
491
527
 
492
- set_all_interpreter_context(nil, nil, resolved_arguments, nil)
528
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null)
529
+ end
530
+ end
493
531
 
494
- # Optimize for the case that field is selected only once
495
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
496
- next_selections = ast_node.selections
497
- directives = ast_node.directives
498
- else
499
- next_selections = []
500
- directives = []
501
- field_ast_nodes.each { |f|
502
- next_selections.concat(f.selections)
503
- directives.concat(f.directives)
504
- }
505
- end
506
-
507
- field_result = call_method_on_directives(:resolve, object, directives) do
508
- # Actually call the field resolver and capture the result
509
- app_result = begin
510
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
511
- field_defn.resolve(object, kwarg_arguments, context)
512
- end
513
- rescue GraphQL::ExecutionError => err
514
- err
515
- rescue StandardError => err
516
- begin
517
- query.handle_or_reraise(err)
518
- rescue GraphQL::ExecutionError => ex_err
519
- ex_err
520
- end
532
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
533
+ st = get_current_runtime_state
534
+ st.current_field = field_defn
535
+ st.current_object = object
536
+ st.current_arguments = resolved_arguments
537
+ st.current_result_name = result_name
538
+ st.current_result = selection_result
539
+ # Optimize for the case that field is selected only once
540
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
541
+ next_selections = ast_node.selections
542
+ directives = ast_node.directives
543
+ else
544
+ next_selections = []
545
+ directives = []
546
+ field_ast_nodes.each { |f|
547
+ next_selections.concat(f.selections)
548
+ directives.concat(f.directives)
549
+ }
550
+ end
551
+
552
+ field_result = call_method_on_directives(:resolve, object, directives) do
553
+ # Actually call the field resolver and capture the result
554
+ app_result = begin
555
+ query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
+ field_defn.resolve(object, kwarg_arguments, context)
521
557
  end
522
- 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|
523
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
524
- if HALT != continue_value
525
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
526
- end
558
+ rescue GraphQL::ExecutionError => err
559
+ err
560
+ rescue StandardError => err
561
+ begin
562
+ query.handle_or_reraise(err)
563
+ rescue GraphQL::ExecutionError => ex_err
564
+ ex_err
527
565
  end
528
566
  end
529
-
530
- # If this field is a root mutation field, immediately resolve
531
- # all of its child fields before moving on to the next root mutation field.
532
- # (Subselections of this mutation will still be resolved level-by-level.)
533
- if is_eager_field
534
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
535
- else
536
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
537
- field_result
567
+ after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568
+ continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
+ if HALT != continue_value
570
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
571
+ end
538
572
  end
539
573
  end
574
+
575
+ # If this field is a root mutation field, immediately resolve
576
+ # all of its child fields before moving on to the next root mutation field.
577
+ # (Subselections of this mutation will still be resolved level-by-level.)
578
+ if is_eager_field
579
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
580
+ else
581
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
582
+ field_result
583
+ end
540
584
  end
541
585
 
586
+
542
587
  def dead_result?(selection_result)
543
- selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
588
+ selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
544
589
  end
545
590
 
546
- def set_result(selection_result, result_name, value)
591
+ def set_result(selection_result, result_name, value, is_child_result)
547
592
  if !dead_result?(selection_result)
548
593
  if value.nil? &&
549
594
  ( # there are two conditions under which `nil` is not allowed in the response:
@@ -563,11 +608,13 @@ module GraphQL
563
608
  if parent.nil? # This is a top-level result hash
564
609
  @response = nil
565
610
  else
566
- set_result(parent, name_in_parent, nil)
611
+ set_result(parent, name_in_parent, nil, false)
567
612
  set_graphql_dead(selection_result)
568
613
  end
614
+ elsif is_child_result
615
+ selection_result.set_child_result(result_name, value)
569
616
  else
570
- selection_result[result_name] = value
617
+ selection_result.set_leaf(result_name, value)
571
618
  end
572
619
  end
573
620
  end
@@ -587,18 +634,29 @@ module GraphQL
587
634
  end
588
635
  end
589
636
 
637
+ def current_path
638
+ st = get_current_runtime_state
639
+ result = st.current_result
640
+ path = result && result.path
641
+ if path && (rn = st.current_result_name)
642
+ path = path.dup
643
+ path.push(rn)
644
+ end
645
+ path
646
+ end
647
+
590
648
  HALT = Object.new
591
- def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
649
+ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
592
650
  case value
593
651
  when nil
594
652
  if is_non_null
595
- set_result(selection_result, result_name, nil) do
653
+ set_result(selection_result, result_name, nil, false) do
596
654
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
597
655
  err = parent_type::InvalidNullError.new(parent_type, field, value)
598
656
  schema.type_error(err, context)
599
657
  end
600
658
  else
601
- set_result(selection_result, result_name, nil)
659
+ set_result(selection_result, result_name, nil, false)
602
660
  end
603
661
  HALT
604
662
  when GraphQL::Error
@@ -607,14 +665,24 @@ module GraphQL
607
665
  # every time.
608
666
  if value.is_a?(GraphQL::ExecutionError)
609
667
  if selection_result.nil? || !dead_result?(selection_result)
610
- value.path ||= path
668
+ value.path ||= current_path
611
669
  value.ast_node ||= ast_node
612
670
  context.errors << value
613
671
  if selection_result
614
- set_result(selection_result, result_name, nil)
672
+ set_result(selection_result, result_name, nil, false)
615
673
  end
616
674
  end
617
675
  HALT
676
+ elsif value.is_a?(GraphQL::UnauthorizedFieldError)
677
+ value.field ||= field
678
+ # this hook might raise & crash, or it might return
679
+ # a replacement value
680
+ next_value = begin
681
+ schema.unauthorized_field(value)
682
+ rescue GraphQL::ExecutionError => err
683
+ err
684
+ end
685
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
618
686
  elsif value.is_a?(GraphQL::UnauthorizedError)
619
687
  # this hook might raise & crash, or it might return
620
688
  # a replacement value
@@ -623,7 +691,7 @@ module GraphQL
623
691
  rescue GraphQL::ExecutionError => err
624
692
  err
625
693
  end
626
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
694
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
627
695
  elsif GraphQL::Execution::SKIP == value
628
696
  # It's possible a lazy was already written here
629
697
  case selection_result
@@ -649,15 +717,15 @@ module GraphQL
649
717
  if selection_result.nil? || !dead_result?(selection_result)
650
718
  value.each_with_index do |error, index|
651
719
  error.ast_node ||= ast_node
652
- error.path ||= path + (list_type_at_all ? [index] : [])
720
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
653
721
  context.errors << error
654
722
  end
655
723
  if selection_result
656
724
  if list_type_at_all
657
725
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
658
- set_result(selection_result, result_name, result_without_errors)
726
+ set_result(selection_result, result_name, result_without_errors, false)
659
727
  else
660
- set_result(selection_result, result_name, nil)
728
+ set_result(selection_result, result_name, nil, false)
661
729
  end
662
730
  end
663
731
  end
@@ -667,7 +735,7 @@ module GraphQL
667
735
  end
668
736
  when GraphQL::Execution::Interpreter::RawValue
669
737
  # Write raw value directly to the response without resolving nested objects
670
- set_result(selection_result, result_name, value.resolve)
738
+ set_result(selection_result, result_name, value.resolve, false)
671
739
  HALT
672
740
  else
673
741
  value
@@ -682,7 +750,7 @@ module GraphQL
682
750
  # Location information from `path` and `ast_node`.
683
751
  #
684
752
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
685
- 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
753
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
686
754
  if current_type.non_null?
687
755
  current_type = current_type.of_type
688
756
  is_non_null = true
@@ -695,11 +763,11 @@ module GraphQL
695
763
  rescue StandardError => err
696
764
  schema.handle_or_reraise(context, err)
697
765
  end
698
- set_result(selection_result, result_name, r)
766
+ set_result(selection_result, result_name, r, false)
699
767
  r
700
768
  when "UNION", "INTERFACE"
701
- resolved_type_or_lazy = resolve_type(current_type, value, path)
702
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
769
+ resolved_type_or_lazy = resolve_type(current_type, value)
770
+ after_lazy(resolved_type_or_lazy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
703
771
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
704
772
  resolved_type, resolved_value = resolved_type_result
705
773
  else
@@ -713,10 +781,10 @@ module GraphQL
713
781
  err_class = current_type::UnresolvedTypeError
714
782
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
715
783
  schema.type_error(type_error, context)
716
- set_result(selection_result, result_name, nil)
784
+ set_result(selection_result, result_name, nil, false)
717
785
  nil
718
786
  else
719
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
787
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
720
788
  end
721
789
  end
722
790
  when "OBJECT"
@@ -725,11 +793,11 @@ module GraphQL
725
793
  rescue GraphQL::ExecutionError => err
726
794
  err
727
795
  end
728
- after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
729
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
796
+ after_lazy(object_proxy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
797
+ continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
730
798
  if HALT != continue_value
731
799
  response_hash = GraphQLResultHash.new(result_name, selection_result)
732
- set_result(selection_result, result_name, response_hash)
800
+ set_result(selection_result, result_name, response_hash, true)
733
801
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
734
802
  # There are two possibilities for `gathered_selections`:
735
803
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -747,10 +815,15 @@ module GraphQL
747
815
  this_result = response_hash
748
816
  final_result = nil
749
817
  end
750
- set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
818
+ # reset this mutable state
819
+ # Unset `result_name` here because it's already included in the new response hash
820
+ st = get_current_runtime_state
821
+ st.current_object = continue_value
822
+ st.current_result_name = nil
823
+ st.current_result = this_result
824
+
751
825
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
752
826
  evaluate_selections(
753
- path,
754
827
  continue_value,
755
828
  current_type,
756
829
  false,
@@ -768,43 +841,30 @@ module GraphQL
768
841
  inner_type = current_type.of_type
769
842
  # This is true for objects, unions, and interfaces
770
843
  use_dataloader_job = !inner_type.unwrap.kind.input?
844
+ inner_type_non_null = inner_type.non_null?
771
845
  response_list = GraphQLResultArray.new(result_name, selection_result)
772
- response_list.graphql_non_null_list_items = inner_type.non_null?
773
- set_result(selection_result, result_name, response_list)
774
- result_was_set = false
846
+ response_list.graphql_non_null_list_items = inner_type_non_null
847
+ set_result(selection_result, result_name, response_list, true)
775
848
  idx = 0
776
849
  list_value = begin
777
850
  value.each do |inner_value|
778
- break if dead_result?(response_list)
779
- if !result_was_set
780
- # Don't set the result unless `.each` is successful
781
- set_result(selection_result, result_name, response_list)
782
- result_was_set = true
783
- end
784
- next_path = path + [idx]
785
851
  this_idx = idx
786
- next_path.freeze
787
852
  idx += 1
788
853
  if use_dataloader_job
789
854
  @dataloader.append_job do
790
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
855
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
791
856
  end
792
857
  else
793
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
858
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
794
859
  end
795
860
  end
796
- # Maybe the list was empty and the block was never called.
797
- if !result_was_set
798
- set_result(selection_result, result_name, response_list)
799
- result_was_set = true
800
- end
801
861
 
802
862
  response_list
803
863
  rescue NoMethodError => err
804
864
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
805
865
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
806
866
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
807
- raise ListResultFailedError.new(value: value, field: field, path: path)
867
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
808
868
  else
809
869
  # This was some other NoMethodError -- let it bubble to reveal the real error.
810
870
  raise
@@ -819,20 +879,22 @@ module GraphQL
819
879
  end
820
880
  end
821
881
 
822
- continue_value(path, list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
882
+ continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
823
883
  else
824
884
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
825
885
  end
826
886
  end
827
887
 
828
- def resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
829
- set_all_interpreter_context(nil, nil, nil, next_path)
888
+ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
889
+ st = get_current_runtime_state
890
+ st.current_result_name = this_idx
891
+ st.current_result = response_list
830
892
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
831
893
  # This will update `response_list` with the lazy
832
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
833
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
894
+ after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
895
+ continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
834
896
  if HALT != continue_value
835
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
897
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
836
898
  end
837
899
  end
838
900
  end
@@ -851,7 +913,6 @@ module GraphQL
851
913
  dir_defn = @schema_directives.fetch(dir_node.name)
852
914
  raw_dir_args = arguments(nil, dir_defn, dir_node)
853
915
  dir_args = continue_value(
854
- @context[:current_path], # path
855
916
  raw_dir_args, # value
856
917
  dir_defn, # parent_type
857
918
  nil, # field
@@ -883,37 +944,30 @@ module GraphQL
883
944
  true
884
945
  end
885
946
 
886
- def set_all_interpreter_context(object, field, arguments, path)
887
- ti = thread_info
888
- if object
889
- ti[:current_object] = object
890
- end
891
- if field
892
- ti[:current_field] = field
893
- end
894
- if arguments
895
- ti[:current_arguments] = arguments
896
- end
897
- if path
898
- ti[:current_path] = path
899
- end
947
+ def get_current_runtime_state
948
+ Thread.current[:__graphql_runtime_info] ||= CurrentState.new
900
949
  end
901
950
 
902
951
  # @param obj [Object] Some user-returned value that may want to be batched
903
- # @param path [Array<String>]
904
952
  # @param field [GraphQL::Schema::Field]
905
953
  # @param eager [Boolean] Set to `true` for mutation root fields only
906
954
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
907
955
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
908
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
956
+ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
909
957
  if lazy?(lazy_obj)
910
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
911
- set_all_interpreter_context(owner_object, field, arguments, path)
958
+ orig_result = result
959
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
960
+ st = get_current_runtime_state
961
+ st.current_object = owner_object
962
+ st.current_field = field
963
+ st.current_arguments = arguments
964
+ st.current_result_name = result_name
965
+ st.current_result = orig_result
912
966
  # Wrap the execution of _this_ method with tracing,
913
967
  # but don't wrap the continuation below
914
968
  inner_obj = begin
915
969
  if trace
916
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
970
+ query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
917
971
  schema.sync_lazy(lazy_obj)
918
972
  end
919
973
  else
@@ -934,11 +988,17 @@ module GraphQL
934
988
  if eager
935
989
  lazy.value
936
990
  else
937
- set_result(result, result_name, lazy)
991
+ set_result(result, result_name, lazy, false)
992
+ current_depth = 0
993
+ while result
994
+ current_depth += 1
995
+ result = result.graphql_parent
996
+ end
997
+ @lazies_at_depth[current_depth] << lazy
938
998
  lazy
939
999
  end
940
1000
  else
941
- set_all_interpreter_context(owner_object, field, arguments, path)
1001
+ # Don't need to reset state here because it _wasn't_ lazy.
942
1002
  yield(lazy_obj)
943
1003
  end
944
1004
  end
@@ -952,25 +1012,18 @@ module GraphQL
952
1012
  end
953
1013
  end
954
1014
 
955
- # Set this pair in the Query context, but also in the interpeter namespace,
956
- # for compatibility.
957
- def set_interpreter_context(key, value)
958
- thread_info[key] = value
959
- end
960
-
961
- def delete_interpreter_context(key)
962
- (ti = thread_info) && ti.delete(key)
1015
+ def delete_all_interpreter_context
1016
+ Thread.current[:__graphql_runtime_info] = nil
963
1017
  end
964
1018
 
965
- def resolve_type(type, value, path)
966
- trace_payload = { context: context, type: type, object: value, path: path }
967
- resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
1019
+ def resolve_type(type, value)
1020
+ resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
968
1021
  query.resolve_type(type, value)
969
1022
  end
970
1023
 
971
1024
  if lazy?(resolved_type)
972
1025
  GraphQL::Execution::Lazy.new do
973
- query.trace("resolve_type_lazy", trace_payload) do
1026
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
974
1027
  schema.sync_lazy(resolved_type)
975
1028
  end
976
1029
  end