graphql 2.4.5 → 2.5.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +37 -40
  4. data/lib/graphql/analysis.rb +12 -9
  5. data/lib/graphql/autoload.rb +1 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +5 -0
  9. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  10. data/lib/graphql/dashboard/installable.rb +22 -0
  11. data/lib/graphql/dashboard/limiters.rb +93 -0
  12. data/lib/graphql/dashboard/operation_store.rb +199 -0
  13. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  15. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  16. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  17. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  18. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  19. data/lib/graphql/dashboard/statics/icon.png +0 -0
  20. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  36. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  37. data/lib/graphql/dashboard.rb +158 -0
  38. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  39. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  40. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +2 -2
  43. data/lib/graphql/dataloader.rb +37 -5
  44. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  45. data/lib/graphql/execution/interpreter/runtime.rb +71 -34
  46. data/lib/graphql/execution/interpreter.rb +12 -3
  47. data/lib/graphql/execution/multiplex.rb +1 -5
  48. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  49. data/lib/graphql/invalid_name_error.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +5 -15
  51. data/lib/graphql/language/lexer.rb +7 -3
  52. data/lib/graphql/language/nodes.rb +3 -0
  53. data/lib/graphql/language/parser.rb +15 -8
  54. data/lib/graphql/language/static_visitor.rb +37 -33
  55. data/lib/graphql/language/visitor.rb +59 -55
  56. data/lib/graphql/query.rb +7 -13
  57. data/lib/graphql/railtie.rb +1 -1
  58. data/lib/graphql/schema/argument.rb +7 -8
  59. data/lib/graphql/schema/build_from_definition.rb +99 -53
  60. data/lib/graphql/schema/directive.rb +1 -1
  61. data/lib/graphql/schema/enum.rb +36 -1
  62. data/lib/graphql/schema/enum_value.rb +1 -1
  63. data/lib/graphql/schema/field.rb +2 -2
  64. data/lib/graphql/schema/input_object.rb +16 -16
  65. data/lib/graphql/schema/interface.rb +2 -1
  66. data/lib/graphql/schema/member/has_arguments.rb +13 -5
  67. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  68. data/lib/graphql/schema/member/has_directives.rb +1 -1
  69. data/lib/graphql/schema/member/has_fields.rb +1 -1
  70. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  71. data/lib/graphql/schema/member/scoped.rb +1 -1
  72. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  73. data/lib/graphql/schema/member.rb +1 -0
  74. data/lib/graphql/schema/object.rb +17 -8
  75. data/lib/graphql/schema/resolver.rb +6 -5
  76. data/lib/graphql/schema/subscription.rb +50 -4
  77. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  78. data/lib/graphql/schema/visibility/migration.rb +1 -0
  79. data/lib/graphql/schema/visibility/profile.rb +11 -5
  80. data/lib/graphql/schema/visibility.rb +14 -9
  81. data/lib/graphql/schema/warden.rb +14 -1
  82. data/lib/graphql/schema.rb +63 -35
  83. data/lib/graphql/static_validation/all_rules.rb +1 -1
  84. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  85. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  86. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  87. data/lib/graphql/static_validation/validator.rb +6 -1
  88. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  89. data/lib/graphql/subscriptions/event.rb +12 -1
  90. data/lib/graphql/subscriptions/serialize.rb +1 -1
  91. data/lib/graphql/testing/helpers.rb +5 -2
  92. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  93. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  94. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  95. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  96. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  97. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  98. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  99. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  100. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  101. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  102. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  103. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  104. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  105. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  106. data/lib/graphql/tracing/monitor_trace.rb +285 -0
  107. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  108. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  109. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  110. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  111. data/lib/graphql/tracing/null_trace.rb +9 -0
  112. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  113. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  114. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  115. data/lib/graphql/tracing/platform_trace.rb +5 -0
  116. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  117. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  118. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  119. data/lib/graphql/tracing/scout_trace.rb +32 -55
  120. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  121. data/lib/graphql/tracing/sentry_trace.rb +63 -94
  122. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  123. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  124. data/lib/graphql/tracing/trace.rb +111 -1
  125. data/lib/graphql/tracing.rb +31 -30
  126. data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
  127. data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
  128. data/lib/graphql/version.rb +1 -1
  129. data/lib/graphql.rb +4 -29
  130. metadata +144 -11
  131. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  132. data/lib/graphql/backtrace/trace.rb +0 -93
  133. data/lib/graphql/backtrace/tracer.rb +0 -80
  134. data/lib/graphql/schema/null_mask.rb +0 -11
  135. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -4,6 +4,8 @@ require "graphql/dataloader/null_dataloader"
4
4
  require "graphql/dataloader/request"
5
5
  require "graphql/dataloader/request_all"
6
6
  require "graphql/dataloader/source"
7
+ require "graphql/dataloader/active_record_association_source"
8
+ require "graphql/dataloader/active_record_source"
7
9
 
8
10
  module GraphQL
9
11
  # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
@@ -129,8 +131,11 @@ module GraphQL
129
131
  # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
130
132
  #
131
133
  # @return [void]
132
- def yield
134
+ def yield(source = Fiber[:__graphql_current_dataloader_source])
135
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
136
+ trace&.dataloader_fiber_yield(source)
133
137
  Fiber.yield
138
+ trace&.dataloader_fiber_resume(source)
134
139
  nil
135
140
  end
136
141
 
@@ -184,6 +189,7 @@ module GraphQL
184
189
  end
185
190
 
186
191
  def run
192
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
187
193
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
188
194
  job_fibers = []
189
195
  next_job_fibers = []
@@ -191,10 +197,11 @@ module GraphQL
191
197
  next_source_fibers = []
192
198
  first_pass = true
193
199
  manager = spawn_fiber do
200
+ trace&.begin_dataloader(self)
194
201
  while first_pass || !job_fibers.empty?
195
202
  first_pass = false
196
203
 
197
- while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
204
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
198
205
  if f.alive?
199
206
  finished = run_fiber(f)
200
207
  if !finished
@@ -205,7 +212,7 @@ module GraphQL
205
212
  join_queues(job_fibers, next_job_fibers)
206
213
 
207
214
  while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
208
- while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
215
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
209
216
  if f.alive?
210
217
  finished = run_fiber(f)
211
218
  if !finished
@@ -216,6 +223,8 @@ module GraphQL
216
223
  join_queues(source_fibers, next_source_fibers)
217
224
  end
218
225
  end
226
+
227
+ trace&.end_dataloader(self)
219
228
  end
220
229
 
221
230
  run_fiber(manager)
@@ -230,6 +239,7 @@ module GraphQL
230
239
  if !source_fibers.empty?
231
240
  raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
232
241
  end
242
+
233
243
  rescue UncaughtThrowError => e
234
244
  throw e.tag, e.value
235
245
  end
@@ -247,6 +257,22 @@ module GraphQL
247
257
  }
248
258
  end
249
259
 
260
+ # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere.
261
+ # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper
262
+ # methods, `dataload_record` and `dataload_association`.
263
+ # @param records [Array<ActiveRecord::Base>] Already-loaded records to warm the cache with
264
+ # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource})
265
+ # @return [void]
266
+ def merge_records(records, index_by: :id)
267
+ records_by_class = Hash.new { |h, k| h[k] = {} }
268
+ records.each do |r|
269
+ records_by_class[r.class][r.public_send(index_by)] = r
270
+ end
271
+ records_by_class.each do |r_class, records|
272
+ with(ActiveRecordSource, r_class).merge(records)
273
+ end
274
+ end
275
+
250
276
  private
251
277
 
252
278
  def calculate_fiber_limit
@@ -266,17 +292,19 @@ module GraphQL
266
292
  new_queue.clear
267
293
  end
268
294
 
269
- def spawn_job_fiber
295
+ def spawn_job_fiber(trace)
270
296
  if !@pending_jobs.empty?
271
297
  spawn_fiber do
298
+ trace&.dataloader_spawn_execution_fiber(@pending_jobs)
272
299
  while job = @pending_jobs.shift
273
300
  job.call
274
301
  end
302
+ trace&.dataloader_fiber_exit
275
303
  end
276
304
  end
277
305
  end
278
306
 
279
- def spawn_source_fiber
307
+ def spawn_source_fiber(trace)
280
308
  pending_sources = nil
281
309
  @source_cache.each_value do |source_by_batch_params|
282
310
  source_by_batch_params.each_value do |source|
@@ -289,10 +317,14 @@ module GraphQL
289
317
 
290
318
  if pending_sources
291
319
  spawn_fiber do
320
+ trace&.dataloader_spawn_source_fiber(pending_sources)
292
321
  pending_sources.each do |source|
293
322
  Fiber[:__graphql_current_dataloader_source] = source
323
+ trace&.begin_dataloader_source(source)
294
324
  source.run_pending_keys
325
+ trace&.end_dataloader_source(source)
295
326
  end
327
+ trace&.dataloader_fiber_exit
296
328
  end
297
329
  end
298
330
  end
@@ -5,7 +5,10 @@ module GraphQL
5
5
  class Interpreter
6
6
  class Runtime
7
7
  module GraphQLResult
8
- def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager)
8
+ def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager, ast_node, graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
9
+ @ast_node = ast_node
10
+ @graphql_arguments = graphql_arguments
11
+ @graphql_field = graphql_field
9
12
  @graphql_parent = parent_result
10
13
  @graphql_application_value = application_value
11
14
  @graphql_result_type = result_type
@@ -31,14 +34,14 @@ module GraphQL
31
34
 
32
35
  attr_accessor :graphql_dead
33
36
  attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent,
34
- :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager
37
+ :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field
35
38
 
36
39
  # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
37
40
  attr_accessor :graphql_result_data
38
41
  end
39
42
 
40
43
  class GraphQLResultHash
41
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
44
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
42
45
  super
43
46
  @graphql_result_data = {}
44
47
  end
@@ -126,7 +129,7 @@ module GraphQL
126
129
  class GraphQLResultArray
127
130
  include GraphQLResult
128
131
 
129
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
132
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
130
133
  super
131
134
  @graphql_result_data = []
132
135
  end
@@ -168,6 +171,10 @@ module GraphQL
168
171
  def values
169
172
  (@graphql_metadata || @graphql_result_data)
170
173
  end
174
+
175
+ def [](idx)
176
+ (@graphql_metadata || @graphql_result_data)[idx]
177
+ end
171
178
  end
172
179
  end
173
180
  end
@@ -74,7 +74,7 @@ module GraphQL
74
74
  runtime_object = root_type.wrap(query.root_value, context)
75
75
  runtime_object = schema.sync_lazy(runtime_object)
76
76
  is_eager = root_op_type == "mutation"
77
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
77
+ @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager, root_operation, nil, nil)
78
78
  st = get_current_runtime_state
79
79
  st.current_result = @response
80
80
 
@@ -85,7 +85,7 @@ module GraphQL
85
85
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
86
  each_gathered_selections(@response) do |selections, is_selection_array|
87
87
  if is_selection_array
88
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
88
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager, root_operation, nil, nil)
89
89
  final_response = @response
90
90
  else
91
91
  selection_response = @response
@@ -218,8 +218,10 @@ module GraphQL
218
218
  result_name, field_ast_nodes_or_ast_node, selections_result
219
219
  )
220
220
  finished_jobs += 1
221
- if target_result && finished_jobs == enqueued_jobs
222
- selections_result.merge_into(target_result)
221
+ if finished_jobs == enqueued_jobs
222
+ if target_result
223
+ selections_result.merge_into(target_result)
224
+ end
223
225
  end
224
226
  @dataloader.clear_cache
225
227
  }
@@ -229,8 +231,10 @@ module GraphQL
229
231
  result_name, field_ast_nodes_or_ast_node, selections_result
230
232
  )
231
233
  finished_jobs += 1
232
- if target_result && finished_jobs == enqueued_jobs
233
- selections_result.merge_into(target_result)
234
+ if finished_jobs == enqueued_jobs
235
+ if target_result
236
+ selections_result.merge_into(target_result)
237
+ end
234
238
  end
235
239
  }
236
240
  end
@@ -371,6 +375,7 @@ module GraphQL
371
375
  end
372
376
  # Actually call the field resolver and capture the result
373
377
  app_result = begin
378
+ @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
374
379
  @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
375
380
  field_defn.resolve(object, kwarg_arguments, context)
376
381
  end
@@ -383,6 +388,7 @@ module GraphQL
383
388
  ex_err
384
389
  end
385
390
  end
391
+ @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
386
392
  after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
387
393
  owner_type = selection_result.graphql_result_type
388
394
  return_type = field_defn.type
@@ -391,6 +397,8 @@ module GraphQL
391
397
  was_scoped = runtime_state.was_authorized_by_scope_items
392
398
  runtime_state.was_authorized_by_scope_items = nil
393
399
  continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
400
+ else
401
+ nil
394
402
  end
395
403
  end
396
404
  end
@@ -465,7 +473,7 @@ module GraphQL
465
473
  # When this comes from a list item, use the parent object:
466
474
  parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
467
475
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
468
- err = parent_type::InvalidNullError.new(parent_type, field, value)
476
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node)
469
477
  schema.type_error(err, context)
470
478
  end
471
479
  else
@@ -574,12 +582,22 @@ module GraphQL
574
582
  r = begin
575
583
  current_type.coerce_result(value, context)
576
584
  rescue StandardError => err
577
- schema.handle_or_reraise(context, err)
585
+ query.handle_or_reraise(err)
578
586
  end
579
587
  set_result(selection_result, result_name, r, false, is_non_null)
580
588
  r
581
589
  when "UNION", "INTERFACE"
582
- resolved_type_or_lazy = resolve_type(current_type, value)
590
+ resolved_type_or_lazy = begin
591
+ resolve_type(current_type, value)
592
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
593
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
594
+ rescue StandardError => err
595
+ begin
596
+ query.handle_or_reraise(err)
597
+ rescue GraphQL::ExecutionError => ex_err
598
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
599
+ end
600
+ end
583
601
  after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
584
602
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
585
603
  resolved_type, resolved_value = resolved_type_result
@@ -609,11 +627,11 @@ module GraphQL
609
627
  after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
610
628
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
611
629
  if HALT != continue_value
612
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
630
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
613
631
  set_result(selection_result, result_name, response_hash, true, is_non_null)
614
632
  each_gathered_selections(response_hash) do |selections, is_selection_array|
615
633
  if is_selection_array
616
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
634
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
617
635
  final_result = response_hash
618
636
  else
619
637
  this_result = response_hash
@@ -634,35 +652,43 @@ module GraphQL
634
652
  # This is true for objects, unions, and interfaces
635
653
  use_dataloader_job = !inner_type.unwrap.kind.input?
636
654
  inner_type_non_null = inner_type.non_null?
637
- response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
655
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
638
656
  set_result(selection_result, result_name, response_list, true, is_non_null)
639
657
  idx = nil
640
658
  list_value = begin
641
- value.each do |inner_value|
642
- idx ||= 0
643
- this_idx = idx
644
- idx += 1
645
- if use_dataloader_job
646
- @dataloader.append_job do
659
+ begin
660
+ value.each do |inner_value|
661
+ idx ||= 0
662
+ this_idx = idx
663
+ idx += 1
664
+ if use_dataloader_job
665
+ @dataloader.append_job do
666
+ 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)
667
+ end
668
+ else
647
669
  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)
648
670
  end
649
- else
650
- 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)
651
671
  end
652
- end
653
672
 
654
- response_list
655
- rescue NoMethodError => err
656
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
657
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
658
- # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
659
- raise ListResultFailedError.new(value: value, field: field, path: current_path)
660
- else
661
- # This was some other NoMethodError -- let it bubble to reveal the real error.
662
- raise
673
+ response_list
674
+ rescue NoMethodError => err
675
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
676
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
677
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
678
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
679
+ else
680
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
681
+ raise
682
+ end
683
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
684
+ ex_err
685
+ rescue StandardError => err
686
+ begin
687
+ query.handle_or_reraise(err)
688
+ rescue GraphQL::ExecutionError => ex_err
689
+ ex_err
690
+ end
663
691
  end
664
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
665
- ex_err
666
692
  rescue StandardError => err
667
693
  begin
668
694
  query.handle_or_reraise(err)
@@ -773,8 +799,10 @@ module GraphQL
773
799
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
774
800
  # Wrap the execution of _this_ method with tracing,
775
801
  # but don't wrap the continuation below
802
+ result = nil
776
803
  inner_obj = begin
777
- if trace
804
+ result = if trace
805
+ @current_trace.begin_execute_field(field, owner_object, arguments, query)
778
806
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
779
807
  schema.sync_lazy(lazy_obj)
780
808
  end
@@ -789,6 +817,10 @@ module GraphQL
789
817
  rescue GraphQL::ExecutionError => ex_err
790
818
  ex_err
791
819
  end
820
+ ensure
821
+ if trace
822
+ @current_trace.end_execute_field(field, owner_object, arguments, query, result)
823
+ end
792
824
  end
793
825
  yield(inner_obj, runtime_state)
794
826
  end
@@ -832,14 +864,19 @@ module GraphQL
832
864
  end
833
865
 
834
866
  def resolve_type(type, value)
867
+ @current_trace.begin_resolve_type(type, value, context)
835
868
  resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
836
869
  query.resolve_type(type, value)
837
870
  end
871
+ @current_trace.end_resolve_type(type, value, context, resolved_type)
838
872
 
839
873
  if lazy?(resolved_type)
840
874
  GraphQL::Execution::Lazy.new do
875
+ @current_trace.begin_resolve_type(type, value, context)
841
876
  @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
842
- schema.sync_lazy(resolved_type)
877
+ rt = schema.sync_lazy(resolved_type)
878
+ @current_trace.end_resolve_type(type, value, context, rt)
879
+ rt
843
880
  end
844
881
  end
845
882
  else
@@ -23,7 +23,7 @@ module GraphQL
23
23
  # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
25
  queries = query_options.map do |opts|
26
- case opts
26
+ query = case opts
27
27
  when Hash
28
28
  schema.query_class.new(schema, nil, **opts)
29
29
  when GraphQL::Query
@@ -31,11 +31,15 @@ module GraphQL
31
31
  else
32
32
  raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
33
33
  end
34
+ query
34
35
  end
35
36
 
37
+ return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty?
38
+
36
39
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
40
+ trace = multiplex.current_trace
37
41
  Fiber[:__graphql_current_multiplex] = multiplex
38
- multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
42
+ trace.execute_multiplex(multiplex: multiplex) do
39
43
  schema = multiplex.schema
40
44
  queries = multiplex.queries
41
45
  lazies_at_depth = Hash.new { |h, k| h[k] = [] }
@@ -44,7 +48,10 @@ module GraphQL
44
48
  multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
45
49
  end
46
50
 
51
+ trace.begin_analyze_multiplex(multiplex, multiplex_analyzers)
47
52
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
53
+ trace.end_analyze_multiplex(multiplex, multiplex_analyzers)
54
+
48
55
  begin
49
56
  # Since this is basically the batching context,
50
57
  # share it for a whole multiplex
@@ -53,7 +60,9 @@ module GraphQL
53
60
  results = []
54
61
  queries.each_with_index do |query, idx|
55
62
  if query.subscription? && !query.subscription_update?
56
- query.context.namespace(:subscriptions)[:events] = []
63
+ subs_namespace = query.context.namespace(:subscriptions)
64
+ subs_namespace[:events] = []
65
+ subs_namespace[:subscriptions] = {}
57
66
  end
58
67
  multiplex.dataloader.append_job {
59
68
  operation = query.selected_operation
@@ -32,14 +32,10 @@ module GraphQL
32
32
  @queries = queries
33
33
  @queries.each { |q| q.multiplex = self }
34
34
  @context = context
35
- @current_trace = @context[:trace] || schema.new_trace(multiplex: self)
36
35
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
37
36
  @tracers = schema.tracers + (context[:tracers] || [])
38
- # Support `context: {backtrace: true}`
39
- if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
40
- @tracers << GraphQL::Backtrace::Tracer
41
- end
42
37
  @max_complexity = max_complexity
38
+ @current_trace = context[:trace] ||= schema.new_trace(multiplex: self)
43
39
  end
44
40
  end
45
41
  end
@@ -7,7 +7,7 @@ module GraphQL
7
7
  "a __DirectiveLocation describes one such possible adjacencies."
8
8
 
9
9
  GraphQL::Schema::Directive::LOCATIONS.each do |location|
10
- value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location)
10
+ value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location, value_method: false)
11
11
  end
12
12
  introspection true
13
13
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- class InvalidNameError < GraphQL::ExecutionError
3
+ class InvalidNameError < GraphQL::Error
4
4
  attr_reader :name, :valid_regex
5
5
  def initialize(name, valid_regex)
6
6
  @name = name
@@ -2,33 +2,23 @@
2
2
  module GraphQL
3
3
  # Raised automatically when a field's resolve function returns `nil`
4
4
  # for a non-null field.
5
- class InvalidNullError < GraphQL::RuntimeTypeError
5
+ class InvalidNullError < GraphQL::Error
6
6
  # @return [GraphQL::BaseType] The owner of {#field}
7
7
  attr_reader :parent_type
8
8
 
9
9
  # @return [GraphQL::Field] The field which failed to return a value
10
10
  attr_reader :field
11
11
 
12
- # @return [nil, GraphQL::ExecutionError] The invalid value for this field
13
- attr_reader :value
12
+ # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
13
+ attr_reader :ast_node
14
14
 
15
- def initialize(parent_type, field, value)
15
+ def initialize(parent_type, field, ast_node)
16
16
  @parent_type = parent_type
17
17
  @field = field
18
- @value = value
18
+ @ast_node = ast_node
19
19
  super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
20
20
  end
21
21
 
22
- # @return [Hash] An entry for the response's "errors" key
23
- def to_h
24
- { "message" => message }
25
- end
26
-
27
- # @deprecated always false
28
- def parent_error?
29
- false
30
- end
31
-
32
22
  class << self
33
23
  attr_accessor :parent_class
34
24
 
@@ -13,17 +13,21 @@ module GraphQL
13
13
  @pos = nil
14
14
  @max_tokens = max_tokens || Float::INFINITY
15
15
  @tokens_count = 0
16
+ @finished = false
16
17
  end
17
18
 
18
- def eos?
19
- @scanner.eos?
19
+ def finished?
20
+ @finished
20
21
  end
21
22
 
22
23
  attr_reader :pos, :tokens_count
23
24
 
24
25
  def advance
25
26
  @scanner.skip(IGNORE_REGEXP)
26
- return false if @scanner.eos?
27
+ if @scanner.eos?
28
+ @finished = true
29
+ return false
30
+ end
27
31
  @tokens_count += 1
28
32
  if @tokens_count > @max_tokens
29
33
  raise_parse_error("This query is too large to execute.")
@@ -141,6 +141,8 @@ module GraphQL
141
141
  end
142
142
 
143
143
  class << self
144
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
145
+
144
146
  # Add a default `#visit_method` and `#children_method_name` using the class name
145
147
  def inherited(child_class)
146
148
  super
@@ -343,6 +345,7 @@ module GraphQL
343
345
  RUBY
344
346
  end
345
347
  end
348
+ # rubocop:enable Development/NoEvalCop
346
349
  end
347
350
  end
348
351
 
@@ -110,7 +110,7 @@ module GraphQL
110
110
  # Only ignored characters is not a valid document
111
111
  raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str)
112
112
  end
113
- while !@lexer.eos?
113
+ while !@lexer.finished?
114
114
  defns << definition
115
115
  end
116
116
  Document.new(pos: 0, definitions: defns, filename: @filename, source: self)
@@ -161,7 +161,7 @@ module GraphQL
161
161
  expect_token(:VAR_SIGN)
162
162
  var_name = parse_name
163
163
  expect_token(:COLON)
164
- var_type = self.type
164
+ var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}")
165
165
  default_value = if at?(:EQUALS)
166
166
  advance_token
167
167
  value
@@ -488,26 +488,33 @@ module GraphQL
488
488
  end
489
489
 
490
490
  def type
491
- type = case token_name
491
+ parsed_type = case token_name
492
492
  when :IDENTIFIER
493
493
  parse_type_name
494
494
  when :LBRACKET
495
495
  list_type
496
+ else
497
+ nil
496
498
  end
497
499
 
498
- if at?(:BANG)
499
- type = Nodes::NonNullType.new(pos: pos, of_type: type, source: self)
500
+ if at?(:BANG) && parsed_type
501
+ parsed_type = Nodes::NonNullType.new(pos: pos, of_type: parsed_type, source: self)
500
502
  expect_token(:BANG)
501
503
  end
502
- type
504
+ parsed_type
503
505
  end
504
506
 
505
507
  def list_type
506
508
  loc = pos
507
509
  expect_token(:LBRACKET)
508
- type = Nodes::ListType.new(pos: loc, of_type: self.type, source: self)
510
+ inner_type = self.type
511
+ parsed_list_type = if inner_type
512
+ Nodes::ListType.new(pos: loc, of_type: inner_type, source: self)
513
+ else
514
+ nil
515
+ end
509
516
  expect_token(:RBRACKET)
510
- type
517
+ parsed_list_type
511
518
  end
512
519
 
513
520
  def parse_operation_type