graphql 2.0.27 → 2.1.3

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  22. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  23. data/lib/graphql/analysis/ast.rb +15 -11
  24. data/lib/graphql/dataloader/source.rb +7 -0
  25. data/lib/graphql/dataloader.rb +38 -10
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  27. data/lib/graphql/execution/interpreter/runtime.rb +95 -254
  28. data/lib/graphql/execution/interpreter.rb +0 -6
  29. data/lib/graphql/execution/lookahead.rb +1 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  31. data/lib/graphql/introspection/entry_points.rb +2 -2
  32. data/lib/graphql/language/block_string.rb +28 -16
  33. data/lib/graphql/language/definition_slice.rb +1 -1
  34. data/lib/graphql/language/document_from_schema_definition.rb +36 -35
  35. data/lib/graphql/language/nodes.rb +2 -2
  36. data/lib/graphql/language/printer.rb +294 -145
  37. data/lib/graphql/language/sanitized_printer.rb +20 -22
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +20 -81
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/pagination/connection.rb +23 -1
  42. data/lib/graphql/query/context/scoped_context.rb +101 -0
  43. data/lib/graphql/query/context.rb +32 -98
  44. data/lib/graphql/query.rb +2 -19
  45. data/lib/graphql/rake_task.rb +3 -12
  46. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  47. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  48. data/lib/graphql/schema/field/scope_extension.rb +7 -1
  49. data/lib/graphql/schema/field.rb +7 -4
  50. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  51. data/lib/graphql/schema/introspection_system.rb +2 -0
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  53. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  54. data/lib/graphql/schema/member/has_fields.rb +4 -1
  55. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  56. data/lib/graphql/schema/member/scoped.rb +19 -0
  57. data/lib/graphql/schema/object.rb +8 -0
  58. data/lib/graphql/schema/printer.rb +8 -7
  59. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  60. data/lib/graphql/schema/resolver.rb +4 -0
  61. data/lib/graphql/schema/scalar.rb +3 -3
  62. data/lib/graphql/schema/subscription.rb +11 -4
  63. data/lib/graphql/schema/warden.rb +87 -89
  64. data/lib/graphql/schema.rb +125 -55
  65. data/lib/graphql/static_validation/all_rules.rb +1 -1
  66. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  67. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  68. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  69. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  70. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  71. data/lib/graphql/static_validation/validation_context.rb +5 -5
  72. data/lib/graphql/static_validation.rb +0 -1
  73. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  74. data/lib/graphql/subscriptions.rb +11 -6
  75. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  76. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  77. data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
  78. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  79. data/lib/graphql/version.rb +1 -1
  80. data/lib/graphql.rb +1 -2
  81. metadata +23 -20
  82. data/lib/graphql/filter.rb +0 -59
  83. data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/execution/interpreter/runtime/graphql_result"
2
3
 
3
4
  module GraphQL
4
5
  module Execution
@@ -15,171 +16,11 @@ module GraphQL
15
16
  @current_arguments = nil
16
17
  @current_result_name = nil
17
18
  @current_result = nil
19
+ @was_authorized_by_scope_items = nil
18
20
  end
19
21
 
20
22
  attr_accessor :current_result, :current_result_name,
21
- :current_arguments, :current_field, :current_object
22
- end
23
-
24
- module GraphQLResult
25
- def initialize(result_name, parent_result, is_non_null_in_parent)
26
- @graphql_parent = parent_result
27
- if parent_result && parent_result.graphql_dead
28
- @graphql_dead = true
29
- end
30
- @graphql_result_name = result_name
31
- @graphql_is_non_null_in_parent = is_non_null_in_parent
32
- # Jump through some hoops to avoid creating this duplicate storage if at all possible.
33
- @graphql_metadata = nil
34
- end
35
-
36
- def path
37
- @path ||= build_path([])
38
- end
39
-
40
- def build_path(path_array)
41
- graphql_result_name && path_array.unshift(graphql_result_name)
42
- @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
43
- end
44
-
45
- attr_accessor :graphql_dead
46
- attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
47
-
48
- # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
49
- attr_accessor :graphql_result_data
50
- end
51
-
52
- class GraphQLResultHash
53
- def initialize(_result_name, _parent_result, _is_non_null_in_parent)
54
- super
55
- @graphql_result_data = {}
56
- end
57
-
58
- include GraphQLResult
59
-
60
- attr_accessor :graphql_merged_into
61
-
62
- def set_leaf(key, value)
63
- # This is a hack.
64
- # Basically, this object is merged into the root-level result at some point.
65
- # But the problem is, some lazies are created whose closures retain reference to _this_
66
- # object. When those lazies are resolved, they cause an update to this object.
67
- #
68
- # In order to return a proper top-level result, we have to update that top-level result object.
69
- # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
70
- # Yowza.
71
- if (t = @graphql_merged_into)
72
- t.set_leaf(key, value)
73
- end
74
-
75
- @graphql_result_data[key] = value
76
- # keep this up-to-date if it's been initialized
77
- @graphql_metadata && @graphql_metadata[key] = value
78
-
79
- value
80
- end
81
-
82
- def set_child_result(key, value)
83
- if (t = @graphql_merged_into)
84
- t.set_child_result(key, value)
85
- end
86
- @graphql_result_data[key] = value.graphql_result_data
87
- # If we encounter some part of this response that requires metadata tracking,
88
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
89
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
90
- value
91
- end
92
-
93
- def delete(key)
94
- @graphql_metadata && @graphql_metadata.delete(key)
95
- @graphql_result_data.delete(key)
96
- end
97
-
98
- def each
99
- (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
100
- end
101
-
102
- def values
103
- (@graphql_metadata || @graphql_result_data).values
104
- end
105
-
106
- def key?(k)
107
- @graphql_result_data.key?(k)
108
- end
109
-
110
- def [](k)
111
- (@graphql_metadata || @graphql_result_data)[k]
112
- end
113
-
114
- def merge_into(into_result)
115
- self.each do |key, value|
116
- case value
117
- when GraphQLResultHash
118
- next_into = into_result[key]
119
- if next_into
120
- value.merge_into(next_into)
121
- else
122
- into_result.set_child_result(key, value)
123
- end
124
- when GraphQLResultArray
125
- # There's no special handling of arrays because currently, there's no way to split the execution
126
- # of a list over several concurrent flows.
127
- next_result.set_child_result(key, value)
128
- else
129
- # We have to assume that, since this passed the `fields_will_merge` selection,
130
- # that the old and new values are the same.
131
- into_result.set_leaf(key, value)
132
- end
133
- end
134
- @graphql_merged_into = into_result
135
- end
136
- end
137
-
138
- class GraphQLResultArray
139
- include GraphQLResult
140
-
141
- def initialize(_result_name, _parent_result, _is_non_null_in_parent)
142
- super
143
- @graphql_result_data = []
144
- end
145
-
146
- def graphql_skip_at(index)
147
- # Mark this index as dead. It's tricky because some indices may already be storing
148
- # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
149
- # this object has to coordinate incoming writes to account for any already-skipped indices.
150
- @skip_indices ||= []
151
- @skip_indices << index
152
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
153
- delete_at_index = index - offset_by
154
- @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
155
- @graphql_result_data.delete_at(delete_at_index)
156
- end
157
-
158
- def set_leaf(idx, value)
159
- if @skip_indices
160
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
161
- idx -= offset_by
162
- end
163
- @graphql_result_data[idx] = value
164
- @graphql_metadata && @graphql_metadata[idx] = value
165
- value
166
- end
167
-
168
- def set_child_result(idx, value)
169
- if @skip_indices
170
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
171
- idx -= offset_by
172
- end
173
- @graphql_result_data[idx] = value.graphql_result_data
174
- # If we encounter some part of this response that requires metadata tracking,
175
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
176
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
177
- value
178
- end
179
-
180
- def values
181
- (@graphql_metadata || @graphql_result_data)
182
- end
23
+ :current_arguments, :current_field, :current_object, :was_authorized_by_scope_items
183
24
  end
184
25
 
185
26
  # @return [GraphQL::Query]
@@ -208,15 +49,16 @@ module GraphQL
208
49
  @runtime_directive_names << name
209
50
  end
210
51
  end
211
- # A cache of { Class => { String => Schema::Field } }
212
- # Which assumes that MyObject.get_field("myField") will return the same field
213
- # during the lifetime of a query
214
- @fields_cache = Hash.new { |h, k| h[k] = {} }
215
- # this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
216
- @fields_cache.compare_by_identity
217
52
  # { Class => Boolean }
218
53
  @lazy_cache = {}
219
54
  @lazy_cache.compare_by_identity
55
+
56
+ @gathered_selections_cache = Hash.new { |h, k|
57
+ cache = {}
58
+ cache.compare_by_identity
59
+ h[k] = cache
60
+ }
61
+ @gathered_selections_cache.compare_by_identity
220
62
  end
221
63
 
222
64
  def final_result
@@ -244,6 +86,7 @@ module GraphQL
244
86
  root_operation = query.selected_operation
245
87
  root_op_type = root_operation.operation_type || "query"
246
88
  root_type = schema.root_type_for_operation(root_op_type)
89
+
247
90
  st = get_current_runtime_state
248
91
  st.current_object = query.root_value
249
92
  st.current_result = @response
@@ -255,7 +98,7 @@ module GraphQL
255
98
  @response = nil
256
99
  else
257
100
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
258
- gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
101
+ gathered_selections = gather_selections(runtime_object, root_type, nil, root_operation.selections)
259
102
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
260
103
  # require isolation during execution (because of runtime directives). In that case,
261
104
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -275,6 +118,7 @@ module GraphQL
275
118
  @dataloader.append_job {
276
119
  st = get_current_runtime_state
277
120
  st.current_object = query.root_value
121
+ st.current_result_name = nil
278
122
  st.current_result = selection_response
279
123
  # This is a less-frequent case; use a fast check since it's often not there.
280
124
  if (directives = selections[:graphql_directives])
@@ -289,20 +133,28 @@ module GraphQL
289
133
  selection_response,
290
134
  final_response,
291
135
  nil,
136
+ st,
292
137
  )
293
138
  end
294
139
  }
295
140
  end
296
141
  end
297
142
  end
298
- delete_all_interpreter_context
299
143
  nil
300
144
  end
301
145
 
302
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
146
+ def gather_selections(owner_object, owner_type, ast_node_for_caching, selections, selections_to_run = nil, selections_by_name = nil)
147
+ if ast_node_for_caching && (cached_selections = @gathered_selections_cache[ast_node_for_caching][owner_type])
148
+ return cached_selections
149
+ end
150
+ selections_by_name ||= {} # allocate this default here so we check the cache first
151
+
152
+ should_cache = true
153
+
303
154
  selections.each do |node|
304
155
  # Skip gathering this if the directive says so
305
156
  if !directives_include?(node, owner_object, owner_type)
157
+ should_cache = false
306
158
  next
307
159
  end
308
160
 
@@ -327,6 +179,7 @@ module GraphQL
327
179
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
328
180
  next_selections = {}
329
181
  next_selections[:graphql_directives] = node.directives
182
+ should_cache = false
330
183
  if selections_to_run
331
184
  selections_to_run << next_selections
332
185
  else
@@ -344,41 +197,41 @@ module GraphQL
344
197
  type_defn = schema.get_type(node.type.name, context)
345
198
 
346
199
  if query.warden.possible_types(type_defn).include?(owner_type)
347
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
200
+ gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
348
201
  end
349
202
  else
350
203
  # it's an untyped fragment, definitely continue
351
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
204
+ gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
352
205
  end
353
206
  when GraphQL::Language::Nodes::FragmentSpread
354
207
  fragment_def = query.fragments[node.name]
355
208
  type_defn = query.get_type(fragment_def.type.name)
356
209
  if query.warden.possible_types(type_defn).include?(owner_type)
357
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
210
+ gather_selections(owner_object, owner_type, nil, fragment_def.selections, selections_to_run, next_selections)
358
211
  end
359
212
  else
360
213
  raise "Invariant: unexpected selection class: #{node.class}"
361
214
  end
362
215
  end
363
216
  end
364
- selections_to_run || selections_by_name
217
+ result = selections_to_run || selections_by_name
218
+ if should_cache
219
+ @gathered_selections_cache[ast_node_for_caching][owner_type] = result
220
+ end
221
+ result
365
222
  end
366
223
 
367
224
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
368
225
 
369
226
  # @return [void]
370
- def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
371
- st = get_current_runtime_state
372
- st.current_object = owner_object
373
- st.current_result_name = nil
374
- st.current_result = selections_result
375
-
227
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
376
228
  finished_jobs = 0
377
229
  enqueued_jobs = gathered_selections.size
378
230
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
379
231
  @dataloader.append_job {
232
+ runtime_state = get_current_runtime_state
380
233
  evaluate_selection(
381
- result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
234
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object, runtime_state
382
235
  )
383
236
  finished_jobs += 1
384
237
  if target_result && finished_jobs == enqueued_jobs
@@ -389,7 +242,9 @@ module GraphQL
389
242
  # so it wouldn't get to the `Resolve` call that happens below.
390
243
  # So instead trigger a run from this outer context.
391
244
  if is_eager_selection
245
+ @dataloader.clear_cache
392
246
  @dataloader.run
247
+ @dataloader.clear_cache
393
248
  end
394
249
  end
395
250
 
@@ -397,7 +252,7 @@ module GraphQL
397
252
  end
398
253
 
399
254
  # @return [void]
400
- 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
255
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
401
256
  return if dead_result?(selections_result)
402
257
  # As a performance optimization, the hash key will be a `Node` if
403
258
  # there's only one selection of the field. But if there are multiple
@@ -410,57 +265,38 @@ module GraphQL
410
265
  ast_node = field_ast_nodes_or_ast_node
411
266
  end
412
267
  field_name = ast_node.name
413
- # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
414
- # because of how `is_introspection` is used to call `.authorized_new` later on.
415
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
416
- is_introspection = false
417
- if field_defn.nil?
418
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
419
- is_introspection = true
420
- entry_point_field
421
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
422
- is_introspection = true
423
- dynamic_field
424
- else
425
- raise "Invariant: no field for #{owner_type}.#{field_name}"
426
- end
427
- end
268
+ field_defn = query.warden.get_field(owner_type, field_name)
428
269
 
429
- return_type = field_defn.type
430
-
431
- # This seems janky, but we need to know
432
- # the field's return type at this path in order
433
- # to propagate `null`
434
- return_type_non_null = return_type.non_null?
435
270
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
436
- st = get_current_runtime_state
437
- st.current_field = field_defn
438
- st.current_result = selections_result
439
- st.current_result_name = result_name
271
+ runtime_state.current_field = field_defn
272
+ runtime_state.current_result = selections_result
273
+ runtime_state.current_result_name = result_name
440
274
 
441
- if is_introspection
275
+ if field_defn.dynamic_introspection
442
276
  owner_object = field_defn.owner.wrap(owner_object, context)
443
277
  end
444
278
 
445
- total_args_count = field_defn.arguments(context).size
446
- if total_args_count == 0
279
+ return_type = field_defn.type
280
+ if !field_defn.any_arguments?
447
281
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
448
282
  if field_defn.extras.size == 0
449
283
  evaluate_selection_with_resolved_keyword_args(
450
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
284
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
451
285
  )
452
286
  else
453
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
287
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
454
288
  end
455
289
  else
456
290
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
457
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
291
+ runtime_state = get_current_runtime_state # This might be in a different fiber
292
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
458
293
  end
459
294
  end
460
295
  end
461
296
 
462
- 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
463
- after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
297
+ 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, runtime_state) # rubocop:disable Metrics/ParameterLists
298
+ after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
299
+ return_type_non_null = return_type.non_null?
464
300
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
465
301
  continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
466
302
  next
@@ -511,17 +347,16 @@ module GraphQL
511
347
  resolved_arguments.keyword_arguments
512
348
  end
513
349
 
514
- 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)
350
+ 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, runtime_state)
515
351
  end
516
352
  end
517
353
 
518
- 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
519
- st = get_current_runtime_state
520
- st.current_field = field_defn
521
- st.current_object = object
522
- st.current_arguments = resolved_arguments
523
- st.current_result_name = result_name
524
- st.current_result = selection_result
354
+ 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, runtime_state) # rubocop:disable Metrics/ParameterLists
355
+ runtime_state.current_field = field_defn
356
+ runtime_state.current_object = object
357
+ runtime_state.current_arguments = resolved_arguments
358
+ runtime_state.current_result_name = result_name
359
+ runtime_state.current_result = selection_result
525
360
  # Optimize for the case that field is selected only once
526
361
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
527
362
  next_selections = ast_node.selections
@@ -550,10 +385,12 @@ module GraphQL
550
385
  ex_err
551
386
  end
552
387
  end
553
- after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
388
+ 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|
554
389
  continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
555
390
  if HALT != continue_value
556
- continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
391
+ was_scoped = runtime_state.was_authorized_by_scope_items
392
+ runtime_state.was_authorized_by_scope_items = nil
393
+ 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)
557
394
  end
558
395
  end
559
396
  end
@@ -733,7 +570,7 @@ module GraphQL
733
570
  # Location information from `path` and `ast_node`.
734
571
  #
735
572
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
736
- 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
573
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
737
574
  if current_type.non_null?
738
575
  current_type = current_type.of_type
739
576
  is_non_null = true
@@ -750,7 +587,7 @@ module GraphQL
750
587
  r
751
588
  when "UNION", "INTERFACE"
752
589
  resolved_type_or_lazy = resolve_type(current_type, value)
753
- 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) do |resolved_type_result|
590
+ 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|
754
591
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
755
592
  resolved_type, resolved_value = resolved_type_result
756
593
  else
@@ -767,21 +604,22 @@ module GraphQL
767
604
  set_result(selection_result, result_name, nil, false, is_non_null)
768
605
  nil
769
606
  else
770
- continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
607
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state)
771
608
  end
772
609
  end
773
610
  when "OBJECT"
774
611
  object_proxy = begin
775
- current_type.wrap(value, context)
612
+ was_scoped ? current_type.wrap_scoped(value, context) : current_type.wrap(value, context)
776
613
  rescue GraphQL::ExecutionError => err
777
614
  err
778
615
  end
779
- 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) do |inner_object|
616
+ 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|
780
617
  continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
781
618
  if HALT != continue_value
782
619
  response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
783
620
  set_result(selection_result, result_name, response_hash, true, is_non_null)
784
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
621
+
622
+ gathered_selections = gather_selections(continue_value, current_type, ast_node, next_selections)
785
623
  # There are two possibilities for `gathered_selections`:
786
624
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
787
625
  # This case is handled below, and the result can be written right into the main `response_hash` above.
@@ -800,11 +638,9 @@ module GraphQL
800
638
  end
801
639
  # reset this mutable state
802
640
  # Unset `result_name` here because it's already included in the new response hash
803
- st = get_current_runtime_state
804
- st.current_object = continue_value
805
- st.current_result_name = nil
806
- st.current_result = this_result
807
-
641
+ runtime_state.current_object = continue_value
642
+ runtime_state.current_result_name = nil
643
+ runtime_state.current_result = this_result
808
644
  # This is a less-frequent case; use a fast check since it's often not there.
809
645
  if (directives = selections[:graphql_directives])
810
646
  selections.delete(:graphql_directives)
@@ -818,6 +654,7 @@ module GraphQL
818
654
  this_result,
819
655
  final_result,
820
656
  owner_object.object,
657
+ runtime_state,
821
658
  )
822
659
  this_result
823
660
  end
@@ -839,10 +676,10 @@ module GraphQL
839
676
  idx += 1
840
677
  if use_dataloader_job
841
678
  @dataloader.append_job do
842
- 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)
679
+ 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, was_scoped, runtime_state)
843
680
  end
844
681
  else
845
- 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)
682
+ 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, was_scoped, runtime_state)
846
683
  end
847
684
  end
848
685
 
@@ -873,16 +710,15 @@ module GraphQL
873
710
  end
874
711
  end
875
712
 
876
- 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
877
- st = get_current_runtime_state
878
- st.current_result_name = this_idx
879
- st.current_result = response_list
713
+ 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, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
714
+ runtime_state.current_result_name = this_idx
715
+ runtime_state.current_result = response_list
880
716
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
881
717
  # This will update `response_list` with the lazy
882
- after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
718
+ after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
883
719
  continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
884
720
  if HALT != continue_value
885
- continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
721
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
886
722
  end
887
723
  end
888
724
  end
@@ -959,16 +795,21 @@ module GraphQL
959
795
  # @param eager [Boolean] Set to `true` for mutation root fields only
960
796
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
961
797
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
962
- def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
798
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
963
799
  if lazy?(lazy_obj)
964
800
  orig_result = result
801
+ was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
965
802
  lazy = GraphQL::Execution::Lazy.new(field: field) do
966
- st = get_current_runtime_state
967
- st.current_object = owner_object
968
- st.current_field = field
969
- st.current_arguments = arguments
970
- st.current_result_name = result_name
971
- st.current_result = orig_result
803
+ # This block might be called in a new fiber;
804
+ # In that case, this will initialize a new state
805
+ # to avoid conflicting with the parent fiber.
806
+ runtime_state = get_current_runtime_state
807
+ runtime_state.current_object = owner_object
808
+ runtime_state.current_field = field
809
+ runtime_state.current_arguments = arguments
810
+ runtime_state.current_result_name = result_name
811
+ runtime_state.current_result = orig_result
812
+ runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
972
813
  # Wrap the execution of _this_ method with tracing,
973
814
  # but don't wrap the continuation below
974
815
  inner_obj = begin
@@ -988,7 +829,7 @@ module GraphQL
988
829
  ex_err
989
830
  end
990
831
  end
991
- yield(inner_obj)
832
+ yield(inner_obj, runtime_state)
992
833
  end
993
834
 
994
835
  if eager
@@ -1005,7 +846,7 @@ module GraphQL
1005
846
  end
1006
847
  else
1007
848
  # Don't need to reset state here because it _wasn't_ lazy.
1008
- yield(lazy_obj)
849
+ yield(lazy_obj, runtime_state)
1009
850
  end
1010
851
  end
1011
852
 
@@ -98,12 +98,6 @@ module GraphQL
98
98
  multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
99
99
  Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
100
100
  end
101
- queries.each do |query|
102
- runtime = query.context.namespace(:interpreter_runtime)[:runtime]
103
- if runtime
104
- runtime.delete_all_interpreter_context
105
- end
106
- end
107
101
  }
108
102
  multiplex.dataloader.run
109
103
 
@@ -102,7 +102,7 @@ module GraphQL
102
102
  @query.warden
103
103
  .possible_types(selected_type)
104
104
  .map { |t| @query.warden.fields(t) }
105
- .flatten
105
+ .tap(&:flatten!)
106
106
  end
107
107
 
108
108
  if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class DynamicFields < Introspection::BaseObject
5
- field :__typename, String, "The name of this type", null: false
5
+ field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
6
6
 
7
7
  def __typename
8
8
  object.class.graphql_name
@@ -2,8 +2,8 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class EntryPoints < Introspection::BaseObject
5
- field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system" do
5
+ field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
6
+ field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
7
7
  argument :name, String
8
8
  end
9
9