graphql 2.4.3 → 2.5.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +38 -41
  4. data/lib/graphql/analysis.rb +15 -12
  5. data/lib/graphql/autoload.rb +38 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  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 +21 -9
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +3 -3
  43. data/lib/graphql/dataloader.rb +43 -14
  44. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  45. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +94 -51
  47. data/lib/graphql/execution/interpreter.rb +16 -7
  48. data/lib/graphql/execution/multiplex.rb +1 -5
  49. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  50. data/lib/graphql/invalid_name_error.rb +1 -1
  51. data/lib/graphql/invalid_null_error.rb +5 -15
  52. data/lib/graphql/language/cache.rb +13 -0
  53. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  54. data/lib/graphql/language/lexer.rb +11 -4
  55. data/lib/graphql/language/nodes.rb +3 -0
  56. data/lib/graphql/language/parser.rb +15 -8
  57. data/lib/graphql/language/printer.rb +8 -8
  58. data/lib/graphql/language/static_visitor.rb +37 -33
  59. data/lib/graphql/language/visitor.rb +59 -55
  60. data/lib/graphql/pagination/connection.rb +1 -1
  61. data/lib/graphql/query/context/scoped_context.rb +1 -1
  62. data/lib/graphql/query/context.rb +6 -5
  63. data/lib/graphql/query/variable_validation_error.rb +1 -1
  64. data/lib/graphql/query.rb +19 -23
  65. data/lib/graphql/railtie.rb +7 -0
  66. data/lib/graphql/schema/addition.rb +1 -1
  67. data/lib/graphql/schema/argument.rb +7 -8
  68. data/lib/graphql/schema/build_from_definition.rb +99 -53
  69. data/lib/graphql/schema/directive/flagged.rb +3 -1
  70. data/lib/graphql/schema/directive.rb +2 -2
  71. data/lib/graphql/schema/enum.rb +36 -1
  72. data/lib/graphql/schema/enum_value.rb +1 -1
  73. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  74. data/lib/graphql/schema/field.rb +27 -13
  75. data/lib/graphql/schema/field_extension.rb +1 -1
  76. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  77. data/lib/graphql/schema/input_object.rb +77 -40
  78. data/lib/graphql/schema/interface.rb +3 -2
  79. data/lib/graphql/schema/loader.rb +1 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  81. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  82. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  83. data/lib/graphql/schema/member/has_directives.rb +4 -4
  84. data/lib/graphql/schema/member/has_fields.rb +19 -1
  85. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  86. data/lib/graphql/schema/member/has_validators.rb +1 -1
  87. data/lib/graphql/schema/member/scoped.rb +1 -1
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/member.rb +1 -0
  90. data/lib/graphql/schema/object.rb +25 -8
  91. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  92. data/lib/graphql/schema/resolver.rb +12 -10
  93. data/lib/graphql/schema/subscription.rb +52 -6
  94. data/lib/graphql/schema/union.rb +1 -1
  95. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  96. data/lib/graphql/schema/validator.rb +1 -1
  97. data/lib/graphql/schema/visibility/migration.rb +1 -0
  98. data/lib/graphql/schema/visibility/profile.rb +95 -243
  99. data/lib/graphql/schema/visibility/visit.rb +190 -0
  100. data/lib/graphql/schema/visibility.rb +169 -28
  101. data/lib/graphql/schema/warden.rb +18 -5
  102. data/lib/graphql/schema.rb +93 -44
  103. data/lib/graphql/static_validation/all_rules.rb +1 -1
  104. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  106. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  107. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  108. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  110. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  111. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  114. data/lib/graphql/static_validation/validation_context.rb +1 -0
  115. data/lib/graphql/static_validation/validator.rb +6 -1
  116. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  117. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  118. data/lib/graphql/subscriptions/event.rb +12 -1
  119. data/lib/graphql/subscriptions/serialize.rb +1 -1
  120. data/lib/graphql/subscriptions.rb +1 -1
  121. data/lib/graphql/testing/helpers.rb +7 -4
  122. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  123. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  124. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  125. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  126. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  127. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  128. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  129. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  130. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  131. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  132. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  133. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  134. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  135. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  136. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  137. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  138. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  139. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  140. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  141. data/lib/graphql/tracing/null_trace.rb +9 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  143. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  144. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  145. data/lib/graphql/tracing/platform_trace.rb +5 -0
  146. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  147. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  148. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  149. data/lib/graphql/tracing/scout_trace.rb +32 -55
  150. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  151. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  152. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  153. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  154. data/lib/graphql/tracing/trace.rb +111 -1
  155. data/lib/graphql/tracing.rb +31 -30
  156. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  157. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  158. data/lib/graphql/types.rb +18 -11
  159. data/lib/graphql/version.rb +1 -1
  160. data/lib/graphql.rb +55 -47
  161. metadata +146 -11
  162. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  163. data/lib/graphql/backtrace/trace.rb +0 -93
  164. data/lib/graphql/backtrace/tracer.rb +0 -80
  165. data/lib/graphql/schema/null_mask.rb +0 -11
  166. 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}.
@@ -78,10 +80,7 @@ module GraphQL
78
80
  def get_fiber_variables
79
81
  fiber_vars = {}
80
82
  Thread.current.keys.each do |fiber_var_key|
81
- # This variable should be fresh in each new fiber
82
- if fiber_var_key != :__graphql_runtime_info
83
- fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
84
- end
83
+ fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
85
84
  end
86
85
  fiber_vars
87
86
  end
@@ -132,8 +131,11 @@ module GraphQL
132
131
  # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
133
132
  #
134
133
  # @return [void]
135
- 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)
136
137
  Fiber.yield
138
+ trace&.dataloader_fiber_resume(source)
137
139
  nil
138
140
  end
139
141
 
@@ -187,6 +189,7 @@ module GraphQL
187
189
  end
188
190
 
189
191
  def run
192
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
190
193
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
191
194
  job_fibers = []
192
195
  next_job_fibers = []
@@ -194,10 +197,11 @@ module GraphQL
194
197
  next_source_fibers = []
195
198
  first_pass = true
196
199
  manager = spawn_fiber do
197
- while first_pass || job_fibers.any?
200
+ trace&.begin_dataloader(self)
201
+ while first_pass || !job_fibers.empty?
198
202
  first_pass = false
199
203
 
200
- 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))))
201
205
  if f.alive?
202
206
  finished = run_fiber(f)
203
207
  if !finished
@@ -207,8 +211,8 @@ module GraphQL
207
211
  end
208
212
  join_queues(job_fibers, next_job_fibers)
209
213
 
210
- while (source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
211
- 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))
214
+ while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
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)))
212
216
  if f.alive?
213
217
  finished = run_fiber(f)
214
218
  if !finished
@@ -219,6 +223,8 @@ module GraphQL
219
223
  join_queues(source_fibers, next_source_fibers)
220
224
  end
221
225
  end
226
+
227
+ trace&.end_dataloader(self)
222
228
  end
223
229
 
224
230
  run_fiber(manager)
@@ -227,12 +233,13 @@ module GraphQL
227
233
  raise "Invariant: Manager fiber didn't terminate properly."
228
234
  end
229
235
 
230
- if job_fibers.any?
236
+ if !job_fibers.empty?
231
237
  raise "Invariant: job fibers should have exited but #{job_fibers.size} remained"
232
238
  end
233
- if source_fibers.any?
239
+ if !source_fibers.empty?
234
240
  raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
235
241
  end
242
+
236
243
  rescue UncaughtThrowError => e
237
244
  throw e.tag, e.value
238
245
  end
@@ -250,6 +257,22 @@ module GraphQL
250
257
  }
251
258
  end
252
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
+
253
276
  private
254
277
 
255
278
  def calculate_fiber_limit
@@ -269,17 +292,19 @@ module GraphQL
269
292
  new_queue.clear
270
293
  end
271
294
 
272
- def spawn_job_fiber
273
- if @pending_jobs.any?
295
+ def spawn_job_fiber(trace)
296
+ if !@pending_jobs.empty?
274
297
  spawn_fiber do
298
+ trace&.dataloader_spawn_execution_fiber(@pending_jobs)
275
299
  while job = @pending_jobs.shift
276
300
  job.call
277
301
  end
302
+ trace&.dataloader_fiber_exit
278
303
  end
279
304
  end
280
305
  end
281
306
 
282
- def spawn_source_fiber
307
+ def spawn_source_fiber(trace)
283
308
  pending_sources = nil
284
309
  @source_cache.each_value do |source_by_batch_params|
285
310
  source_by_batch_params.each_value do |source|
@@ -292,10 +317,14 @@ module GraphQL
292
317
 
293
318
  if pending_sources
294
319
  spawn_fiber do
320
+ trace&.dataloader_spawn_source_fiber(pending_sources)
295
321
  pending_sources.each do |source|
296
322
  Fiber[:__graphql_current_dataloader_source] = source
323
+ trace&.begin_dataloader_source(source)
297
324
  source.run_pending_keys
325
+ trace&.end_dataloader_source(source)
298
326
  end
327
+ trace&.dataloader_fiber_exit
299
328
  end
300
329
  end
301
330
  end
@@ -22,7 +22,7 @@ module GraphQL
22
22
 
23
23
  if smallest_depth
24
24
  lazies = lazies_at_depth.delete(smallest_depth)
25
- if lazies.any?
25
+ if !lazies.empty?
26
26
  dataloader.append_job {
27
27
  lazies.each(&:value) # resolve these Lazy instances
28
28
  }
@@ -55,7 +55,7 @@ module GraphQL
55
55
  # these approaches.
56
56
  dataloader.run
57
57
  next_results = []
58
- while results.any?
58
+ while !results.empty?
59
59
  result_value = results.shift
60
60
  if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash)
61
61
  results.concat(result_value.values)
@@ -81,7 +81,7 @@ module GraphQL
81
81
  end
82
82
  end
83
83
 
84
- if next_results.any?
84
+ if !next_results.empty?
85
85
  # Any pending data loader jobs may populate the
86
86
  # resutl arrays or result hashes accumulated in
87
87
  # `next_results``. Run those **to completion**
@@ -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,18 +34,21 @@ 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 = {}
47
+ @ordered_result_keys = nil
44
48
  end
45
49
 
50
+ attr_accessor :ordered_result_keys
51
+
46
52
  include GraphQLResult
47
53
 
48
54
  attr_accessor :graphql_merged_into
@@ -60,7 +66,13 @@ module GraphQL
60
66
  t.set_leaf(key, value)
61
67
  end
62
68
 
69
+ before_size = @graphql_result_data.size
63
70
  @graphql_result_data[key] = value
71
+ after_size = @graphql_result_data.size
72
+ if after_size > before_size && @ordered_result_keys[before_size] != key
73
+ fix_result_order
74
+ end
75
+
64
76
  # keep this up-to-date if it's been initialized
65
77
  @graphql_metadata && @graphql_metadata[key] = value
66
78
 
@@ -71,7 +83,13 @@ module GraphQL
71
83
  if (t = @graphql_merged_into)
72
84
  t.set_child_result(key, value)
73
85
  end
86
+ before_size = @graphql_result_data.size
74
87
  @graphql_result_data[key] = value.graphql_result_data
88
+ after_size = @graphql_result_data.size
89
+ if after_size > before_size && @ordered_result_keys[before_size] != key
90
+ fix_result_order
91
+ end
92
+
75
93
  # If we encounter some part of this response that requires metadata tracking,
76
94
  # then create the metadata hash if necessary. It will be kept up-to-date after this.
77
95
  (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
@@ -121,12 +139,20 @@ module GraphQL
121
139
  end
122
140
  @graphql_merged_into = into_result
123
141
  end
142
+
143
+ def fix_result_order
144
+ @ordered_result_keys.each do |k|
145
+ if @graphql_result_data.key?(k)
146
+ @graphql_result_data[k] = @graphql_result_data.delete(k)
147
+ end
148
+ end
149
+ end
124
150
  end
125
151
 
126
152
  class GraphQLResultArray
127
153
  include GraphQLResult
128
154
 
129
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
155
+ 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
156
  super
131
157
  @graphql_result_data = []
132
158
  end
@@ -168,6 +194,10 @@ module GraphQL
168
194
  def values
169
195
  (@graphql_metadata || @graphql_result_data)
170
196
  end
197
+
198
+ def [](idx)
199
+ (@graphql_metadata || @graphql_result_data)[idx]
200
+ end
171
201
  end
172
202
  end
173
203
  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
 
@@ -83,9 +83,11 @@ module GraphQL
83
83
  @response = nil
84
84
  else
85
85
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
- each_gathered_selections(@response) do |selections, is_selection_array|
86
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
87
+ @response.ordered_result_keys ||= ordered_result_keys
87
88
  if is_selection_array
88
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
89
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager, root_operation, nil, nil)
90
+ selection_response.ordered_result_keys = ordered_result_keys
89
91
  final_response = @response
90
92
  else
91
93
  selection_response = @response
@@ -107,17 +109,19 @@ module GraphQL
107
109
  end
108
110
 
109
111
  def each_gathered_selections(response_hash)
110
- gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
112
+ ordered_result_keys = []
113
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
114
+ ordered_result_keys.uniq!
111
115
  if gathered_selections.is_a?(Array)
112
116
  gathered_selections.each do |item|
113
- yield(item, true)
117
+ yield(item, true, ordered_result_keys)
114
118
  end
115
119
  else
116
- yield(gathered_selections, false)
120
+ yield(gathered_selections, false, ordered_result_keys)
117
121
  end
118
122
  end
119
123
 
120
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
124
+ def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
121
125
  selections.each do |node|
122
126
  # Skip gathering this if the directive says so
123
127
  if !directives_include?(node, owner_object, owner_type)
@@ -126,6 +130,7 @@ module GraphQL
126
130
 
127
131
  if node.is_a?(GraphQL::Language::Nodes::Field)
128
132
  response_key = node.alias || node.name
133
+ ordered_result_keys << response_key
129
134
  selections = selections_by_name[response_key]
130
135
  # if there was already a selection of this field,
131
136
  # use an array to hold all selections,
@@ -142,7 +147,7 @@ module GraphQL
142
147
  end
143
148
  else
144
149
  # This is an InlineFragment or a FragmentSpread
145
- if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
150
+ if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
146
151
  next_selections = {}
147
152
  next_selections[:graphql_directives] = node.directives
148
153
  if selections_to_run
@@ -162,14 +167,14 @@ module GraphQL
162
167
  type_defn = query.types.type(node.type.name)
163
168
 
164
169
  if query.types.possible_types(type_defn).include?(owner_type)
165
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
170
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
166
171
  if !result.equal?(next_selections)
167
172
  selections_to_run = result
168
173
  end
169
174
  end
170
175
  else
171
176
  # it's an untyped fragment, definitely continue
172
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
177
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
173
178
  if !result.equal?(next_selections)
174
179
  selections_to_run = result
175
180
  end
@@ -178,7 +183,7 @@ module GraphQL
178
183
  fragment_def = query.fragments[node.name]
179
184
  type_defn = query.types.type(fragment_def.type.name)
180
185
  if query.types.possible_types(type_defn).include?(owner_type)
181
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
186
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
182
187
  if !result.equal?(next_selections)
183
188
  selections_to_run = result
184
189
  end
@@ -207,7 +212,6 @@ module GraphQL
207
212
  finished_jobs = 0
208
213
  enqueued_jobs = gathered_selections.size
209
214
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
210
-
211
215
  # Field resolution may pause the fiber,
212
216
  # so it wouldn't get to the `Resolve` call that happens below.
213
217
  # So instead trigger a run from this outer context.
@@ -218,8 +222,10 @@ module GraphQL
218
222
  result_name, field_ast_nodes_or_ast_node, selections_result
219
223
  )
220
224
  finished_jobs += 1
221
- if target_result && finished_jobs == enqueued_jobs
222
- selections_result.merge_into(target_result)
225
+ if finished_jobs == enqueued_jobs
226
+ if target_result
227
+ selections_result.merge_into(target_result)
228
+ end
223
229
  end
224
230
  @dataloader.clear_cache
225
231
  }
@@ -229,8 +235,10 @@ module GraphQL
229
235
  result_name, field_ast_nodes_or_ast_node, selections_result
230
236
  )
231
237
  finished_jobs += 1
232
- if target_result && finished_jobs == enqueued_jobs
233
- selections_result.merge_into(target_result)
238
+ if finished_jobs == enqueued_jobs
239
+ if target_result
240
+ selections_result.merge_into(target_result)
241
+ end
234
242
  end
235
243
  }
236
244
  end
@@ -332,7 +340,7 @@ module GraphQL
332
340
  extra_args[extra] = field_defn.fetch_extra(extra, context)
333
341
  end
334
342
  end
335
- if extra_args.any?
343
+ if !extra_args.empty?
336
344
  resolved_arguments = resolved_arguments.merge_extras(extra_args)
337
345
  end
338
346
  resolved_arguments.keyword_arguments
@@ -361,7 +369,7 @@ module GraphQL
361
369
  end
362
370
 
363
371
  field_result = call_method_on_directives(:resolve, object, directives) do
364
- if directives.any?
372
+ if !directives.empty?
365
373
  # This might be executed in a different context; reset this info
366
374
  runtime_state = get_current_runtime_state
367
375
  runtime_state.current_field = field_defn
@@ -371,6 +379,7 @@ module GraphQL
371
379
  end
372
380
  # Actually call the field resolver and capture the result
373
381
  app_result = begin
382
+ @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
374
383
  @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
375
384
  field_defn.resolve(object, kwarg_arguments, context)
376
385
  end
@@ -383,6 +392,7 @@ module GraphQL
383
392
  ex_err
384
393
  end
385
394
  end
395
+ @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
386
396
  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
397
  owner_type = selection_result.graphql_result_type
388
398
  return_type = field_defn.type
@@ -391,6 +401,8 @@ module GraphQL
391
401
  was_scoped = runtime_state.was_authorized_by_scope_items
392
402
  runtime_state.was_authorized_by_scope_items = nil
393
403
  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)
404
+ else
405
+ nil
394
406
  end
395
407
  end
396
408
  end
@@ -465,7 +477,7 @@ module GraphQL
465
477
  # When this comes from a list item, use the parent object:
466
478
  parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
467
479
  # 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)
480
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node)
469
481
  schema.type_error(err, context)
470
482
  end
471
483
  else
@@ -525,7 +537,7 @@ module GraphQL
525
537
  end
526
538
  when Array
527
539
  # It's an array full of execution errors; add them all.
528
- if value.any? && value.all?(GraphQL::ExecutionError)
540
+ if !value.empty? && value.all?(GraphQL::ExecutionError)
529
541
  list_type_at_all = (field && (field.type.list?))
530
542
  if selection_result.nil? || !selection_result.graphql_dead
531
543
  value.each_with_index do |error, index|
@@ -574,12 +586,22 @@ module GraphQL
574
586
  r = begin
575
587
  current_type.coerce_result(value, context)
576
588
  rescue StandardError => err
577
- schema.handle_or_reraise(context, err)
589
+ query.handle_or_reraise(err)
578
590
  end
579
591
  set_result(selection_result, result_name, r, false, is_non_null)
580
592
  r
581
593
  when "UNION", "INTERFACE"
582
- resolved_type_or_lazy = resolve_type(current_type, value)
594
+ resolved_type_or_lazy = begin
595
+ resolve_type(current_type, value)
596
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
597
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
598
+ rescue StandardError => err
599
+ begin
600
+ query.handle_or_reraise(err)
601
+ rescue GraphQL::ExecutionError => ex_err
602
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
603
+ end
604
+ end
583
605
  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
606
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
585
607
  resolved_type, resolved_value = resolved_type_result
@@ -609,11 +631,13 @@ module GraphQL
609
631
  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
632
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
611
633
  if HALT != continue_value
612
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
634
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
613
635
  set_result(selection_result, result_name, response_hash, true, is_non_null)
614
- each_gathered_selections(response_hash) do |selections, is_selection_array|
636
+ each_gathered_selections(response_hash) do |selections, is_selection_array, ordered_result_keys|
637
+ response_hash.ordered_result_keys ||= ordered_result_keys
615
638
  if is_selection_array
616
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
639
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
640
+ this_result.ordered_result_keys = ordered_result_keys
617
641
  final_result = response_hash
618
642
  else
619
643
  this_result = response_hash
@@ -634,35 +658,43 @@ module GraphQL
634
658
  # This is true for objects, unions, and interfaces
635
659
  use_dataloader_job = !inner_type.unwrap.kind.input?
636
660
  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)
661
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
638
662
  set_result(selection_result, result_name, response_list, true, is_non_null)
639
663
  idx = nil
640
664
  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
665
+ begin
666
+ value.each do |inner_value|
667
+ idx ||= 0
668
+ this_idx = idx
669
+ idx += 1
670
+ if use_dataloader_job
671
+ @dataloader.append_job do
672
+ 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)
673
+ end
674
+ else
647
675
  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
676
  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
677
  end
652
- end
653
678
 
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
679
+ response_list
680
+ rescue NoMethodError => err
681
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
682
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
683
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
684
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
685
+ else
686
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
687
+ raise
688
+ end
689
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
690
+ ex_err
691
+ rescue StandardError => err
692
+ begin
693
+ query.handle_or_reraise(err)
694
+ rescue GraphQL::ExecutionError => ex_err
695
+ ex_err
696
+ end
663
697
  end
664
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
665
- ex_err
666
698
  rescue StandardError => err
667
699
  begin
668
700
  query.handle_or_reraise(err)
@@ -736,7 +768,7 @@ module GraphQL
736
768
  end
737
769
 
738
770
  def get_current_runtime_state
739
- current_state = Thread.current[:__graphql_runtime_info] ||= {}.compare_by_identity
771
+ current_state = Fiber[:__graphql_runtime_info] ||= {}.compare_by_identity
740
772
  current_state[@query] ||= CurrentState.new
741
773
  end
742
774
 
@@ -773,8 +805,10 @@ module GraphQL
773
805
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
774
806
  # Wrap the execution of _this_ method with tracing,
775
807
  # but don't wrap the continuation below
808
+ result = nil
776
809
  inner_obj = begin
777
- if trace
810
+ result = if trace
811
+ @current_trace.begin_execute_field(field, owner_object, arguments, query)
778
812
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
779
813
  schema.sync_lazy(lazy_obj)
780
814
  end
@@ -789,6 +823,10 @@ module GraphQL
789
823
  rescue GraphQL::ExecutionError => ex_err
790
824
  ex_err
791
825
  end
826
+ ensure
827
+ if trace
828
+ @current_trace.end_execute_field(field, owner_object, arguments, query, result)
829
+ end
792
830
  end
793
831
  yield(inner_obj, runtime_state)
794
832
  end
@@ -821,25 +859,30 @@ module GraphQL
821
859
  end
822
860
 
823
861
  def delete_all_interpreter_context
824
- per_query_state = Thread.current[:__graphql_runtime_info]
862
+ per_query_state = Fiber[:__graphql_runtime_info]
825
863
  if per_query_state
826
864
  per_query_state.delete(@query)
827
865
  if per_query_state.size == 0
828
- Thread.current[:__graphql_runtime_info] = nil
866
+ Fiber[:__graphql_runtime_info] = nil
829
867
  end
830
868
  end
831
869
  nil
832
870
  end
833
871
 
834
872
  def resolve_type(type, value)
873
+ @current_trace.begin_resolve_type(type, value, context)
835
874
  resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
836
875
  query.resolve_type(type, value)
837
876
  end
877
+ @current_trace.end_resolve_type(type, value, context, resolved_type)
838
878
 
839
879
  if lazy?(resolved_type)
840
880
  GraphQL::Execution::Lazy.new do
881
+ @current_trace.begin_resolve_type(type, value, context)
841
882
  @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
842
- schema.sync_lazy(resolved_type)
883
+ rt = schema.sync_lazy(resolved_type)
884
+ @current_trace.end_resolve_type(type, value, context, rt)
885
+ rt
843
886
  end
844
887
  end
845
888
  else